1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-13 01:43:50 +03:00

Add try*/catch and map

This commit is contained in:
ekmartin 2015-09-07 13:07:39 +02:00
parent 0887f47086
commit 0a272a6be5
3 changed files with 243 additions and 2 deletions

View File

@ -10,7 +10,6 @@ defmodule Mal.Core do
"<" => fn [a, b] -> a < b end,
"<=" => fn [a, b] -> a <= b end,
">=" => fn [a, b] -> a >= b end,
"list" => fn args -> args end,
"concat" => &Enum.concat/1,
"list?" => &list?/1,
"empty?" => &empty?/1,
@ -23,8 +22,11 @@ defmodule Mal.Core do
"nth" => &nth/1,
"first" => &first/1,
"rest" => &rest/1,
"map" => &map/1,
"list" => fn args -> args end,
"read-string" => fn [input] -> Mal.Reader.read_str(input) end,
"cons" => fn [prepend, list] -> [prepend | list] end
"cons" => fn [prepend, list] -> [prepend | list] end,
"throw" => fn [arg] -> throw({:error, arg}) end
}
end
@ -86,4 +88,12 @@ defmodule Mal.Core do
def rest([[head | tail]]), do: tail
def rest([[]]), do: []
def map([{:macro, function}, list]), do: do_map(function, list)
def map([{:closure, function}, list]), do: do_map(function, list)
def map([function, list]), do: do_map(function, list)
defp do_map(function, list) do
Enum.map(list, fn arg -> function.([arg]) end)
end
end

View File

@ -6,6 +6,9 @@ defmodule Mal.Printer do
def print_str({:closure, mal}, _), do: inspect(mal)
def print_str({:macro, mal}, _), do: "#Macro<#{inspect(mal)}"
def print_str({:symbol, value}, _), do: value
def print_str({:exception, exception}, print_readably) do
print_str(exception, print_readably)
end
def print_str(mal, false) when is_bitstring(mal), do: mal
def print_str(mal, true) when is_bitstring(mal), do: inspect(mal)

View File

@ -0,0 +1,228 @@
defmodule Mix.Tasks.Step9Try do
def run(args) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
bootstrap(args, env)
load_file(args, env)
main(env)
end
defp load_file([], _env), do: nil
defp load_file([file_name | _args], env) do
read_eval_print("""
(load-file "#{file_name}")
""", env)
exit(:normal)
end
defp bootstrap(args, env) do
# not:
read_eval_print("""
(def! not
(fn* (a) (if a false true)))
""", env)
# load-file:
read_eval_print("""
(def! load-file
(fn* (f)
(eval (read-string (str "(do " (slurp f) ")")))))
""", env)
# cond
read_eval_print("""
(defmacro! cond
(fn* (& xs)
(if (> (count xs) 0)
(list 'if (first xs)
(if (> (count xs) 1)
(nth xs 1)
(throw \"odd number of forms to cond\"))
(cons 'cond (rest (rest xs)))))))"
""", env)
# or:
read_eval_print("""
(defmacro! or
(fn* (& xs)
(if (empty? xs)
nil
(if (= 1 (count xs))
(first xs)
`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))
""", env)
Mal.Env.set(env, "eval", fn [ast] ->
eval(ast, env)
end)
case args do
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
[] -> Mal.Env.set(env, "*ARGV*", [])
end
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, "'#{symbol}' not found"})
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"})
defp quasiquote(ast, _env) when not is_list(ast), do: [{:symbol, "quote"}, ast]
defp quasiquote([], _env), do: [{:symbol, "quote"}, []]
defp quasiquote([{:symbol, "unquote"}, arg], _env), do: arg
defp quasiquote([[{:symbol, "splice-unquote"}, first] | tail], env) do
[{:symbol, "concat"}, first, quasiquote(tail, env)]
end
defp quasiquote([head | tail], env) do
[{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
end
defp macro_call?([{:symbol, key} | _tail], env) do
case Mal.Env.get(env, key) do
{:ok, {:macro, _}} -> true
_ -> false
end
end
defp macro_call?(_ast, _env), do: false
defp do_macro_call([{:symbol, key} | tail], env) do
{:ok, {:macro, macro}} = Mal.Env.get(env, key)
macro.(tail)
|> macroexpand(env)
end
def macroexpand(ast, env) do
if macro_call?(ast, env) do
do_macro_call(ast, env)
else
ast
end
end
def eval(ast, env) when not is_list(ast), do: eval_ast(ast, env)
def eval(ast, env) when is_list(ast) do
case macroexpand(ast, env) do
result when is_list(result) -> eval_list(result, env)
result -> result
end
end
def eval_list([{:symbol, "macroexpand"}, ast], env), do: macroexpand(ast, env)
def eval_list([{: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_list([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
eval(List.last(ast), env)
end
def eval_list([{:symbol, "def!"}, {:symbol, key}, value], env) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
def eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env) do
{:closure, evaluated} = eval(function, env)
macro = {:macro, evaluated}
Mal.Env.set(env, key, macro)
macro
end
def eval_list([{:symbol, "let*"}, bindings, body], env) do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
def eval_list([{: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_list([{:symbol, "quote"}, arg], _env), do: arg
def eval_list([{:symbol, "quasiquote"}, ast], env) do
quasiquote(ast, env)
|> eval(env)
end
# (try* A (catch* B C))
def eval_list([{:symbol, "try*"}, try_form,
[{:symbol, "catch*"}, {:symbol, exception}, catch_form]], env) do
try do
eval(try_form, env)
catch
{:error, message}->
catch_env = Mal.Env.initialize(env)
Mal.Env.set(catch_env, exception, {:exception, message})
eval(catch_form, catch_env)
end
end
def eval_list(ast, env) do
[func | args] = eval_ast(ast, env)
case func do
{:closure, closure} -> closure.(args)
_ -> func.(args)
end
end
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, exception} ->
IO.puts("Error: #{Mal.Printer.print_str(exception)}")
end
end