1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-09 18:06:35 +03:00

Make sure eval of do is in tail position and skip step 5

This commit is contained in:
ekmartin 2015-09-06 14:42:49 +02:00
parent 2559456fba
commit d88bc83145
3 changed files with 115 additions and 6 deletions

View File

@ -32,6 +32,7 @@ EXCLUDE_TESTS += test^c^step5 # segfault
EXCLUDE_TESTS += test^cpp^step5 # completes at 10,000
EXCLUDE_TESTS += test^cs^step5 # fatal stack overflow fault
EXCLUDE_TESTS += test^erlang^step5 # erlang is TCO, test passes
EXCLUDE_TESTS += test^elixir^step5 # elixir is TCO, test passes
EXCLUDE_TESTS += test^fsharp^step5 # completes at 10,000, fatal stack overflow at 100,000
EXCLUDE_TESTS += test^haskell^step5 # test completes
EXCLUDE_TESTS += test^make^step5 # no TCO capability/step

View File

@ -43,21 +43,21 @@ defmodule Mix.Tasks.Step4IfFnDo do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp eval_if_false([], _env), do: nil
defp eval_if_false([body], env), do: eval(body, env)
def eval([{:symbol, "if"}, condition, if_true | if_false], env) do
result = eval(condition, env)
if result == nil or result == false do
eval_if_false(if_false, env)
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(ast, env)
|> List.last
eval_ast(List.delete_at(ast, -1), env)
eval(List.last(ast), env)
end
def eval([{:symbol, "def!"}, {:symbol, key}, value], env) do

View File

@ -0,0 +1,108 @@
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(0)
def read_eval_print(line, env) do
read(line)
|> eval(env)
|> print
catch
{:error, message} -> IO.puts("Error: #{message}")
end
end