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:
parent
2559456fba
commit
d88bc83145
1
Makefile
1
Makefile
@ -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
|
||||
|
@ -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
|
||||
|
108
elixir/lib/mix/tasks/step5_tco.ex
Normal file
108
elixir/lib/mix/tasks/step5_tco.ex
Normal 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
|
Loading…
Reference in New Issue
Block a user