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

Use the new data structures in the other steps

This commit is contained in:
ekmartin 2015-09-09 17:53:47 +02:00
parent 9dd9dd2f59
commit eccae82ac6
8 changed files with 356 additions and 237 deletions

View File

@ -9,31 +9,34 @@ defmodule Mix.Tasks.Step2Eval do
def run(_), do: loop
defp loop do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print
|> IO.puts
loop
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Map.fetch(env, symbol) do
{:ok, value} -> value
:error -> throw({:error, "invalid symbol #{symbol}"})
:error -> throw({:error, "'#{symbol}' not found"})
end
end
@ -43,13 +46,14 @@ defmodule Mix.Tasks.Step2Eval do
Mal.Reader.read_str(input)
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
apply(func, args)
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step3Env do
import Mal.Types
alias Mal.Function
@initial_env %{
"+" => &+/2,
"-" => &-/2,
@ -13,31 +16,34 @@ defmodule Mix.Tasks.Step3Env do
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -47,7 +53,6 @@ defmodule Mix.Tasks.Step3Env do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -56,26 +61,27 @@ defmodule Mix.Tasks.Step3Env do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
apply(func, args)
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step4IfFnDo do
import Mal.Types
alias Mal.Function
def run(_) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -7,35 +10,46 @@ defmodule Mix.Tasks.Step4IfFnDo do
end
defp bootstrap(env) do
read_eval_print("(def! not (fn* (a) (if a false true)))", env)
# not:
read_eval_print("""
(def! not
(fn* (a) (if a false true)))
""", env)
Mal.Env.set(env, "eval", fn [ast] ->
eval(ast, env)
end)
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -45,7 +59,6 @@ defmodule Mix.Tasks.Step4IfFnDo do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -54,7 +67,10 @@ defmodule Mix.Tasks.Step4IfFnDo do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp eval([{:symbol, "if"}, condition, if_true | if_false], env) do
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp 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
@ -66,27 +82,29 @@ defmodule Mix.Tasks.Step4IfFnDo do
end
end
defp eval([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval([{:symbol, "fn*"}, params, body], env)
end
defp eval([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -94,19 +112,17 @@ defmodule Mix.Tasks.Step4IfFnDo do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
{:closure, closure} -> closure.(args)
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step5Tco do
import Mal.Types
alias Mal.Function
def run(_) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -7,35 +10,46 @@ defmodule Mix.Tasks.Step5Tco do
end
defp bootstrap(env) do
read_eval_print("(def! not (fn* (a) (if a false true)))", env)
# not:
read_eval_print("""
(def! not
(fn* (a) (if a false true)))
""", env)
Mal.Env.set(env, "eval", fn [ast] ->
eval(ast, env)
end)
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -45,7 +59,6 @@ defmodule Mix.Tasks.Step5Tco do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -54,7 +67,10 @@ defmodule Mix.Tasks.Step5Tco do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp eval([{:symbol, "if"}, condition, if_true | if_false], env) do
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp 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
@ -66,27 +82,29 @@ defmodule Mix.Tasks.Step5Tco do
end
end
defp eval([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval([{:symbol, "fn*"}, params, body], env)
end
defp eval([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -94,19 +112,17 @@ defmodule Mix.Tasks.Step5Tco do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
{:closure, closure} -> closure.(args)
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step6File do
import Mal.Types
alias Mal.Function
def run(args) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -8,7 +11,7 @@ defmodule Mix.Tasks.Step6File do
end
defp load_file([], _env), do: nil
defp load_file([file_name | args], env) do
defp load_file([file_name | _args], env) do
read_eval_print("""
(load-file "#{file_name}")
""", env)
@ -34,37 +37,40 @@ defmodule Mix.Tasks.Step6File do
end)
case args do
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
[] -> Mal.Env.set(env, "*ARGV*", [])
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", list(rest))
[] -> Mal.Env.set(env, "*ARGV*", list([]))
end
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -74,7 +80,6 @@ defmodule Mix.Tasks.Step6File do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -83,7 +88,10 @@ defmodule Mix.Tasks.Step6File do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp eval([{:symbol, "if"}, condition, if_true | if_false], env) do
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp 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
@ -95,27 +103,29 @@ defmodule Mix.Tasks.Step6File do
end
end
defp eval([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval([{:symbol, "fn*"}, params, body], env)
end
defp eval([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -123,19 +133,17 @@ defmodule Mix.Tasks.Step6File do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
{:closure, closure} -> closure.(args)
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step7Quote do
import Mal.Types
alias Mal.Function
def run(args) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -34,37 +37,40 @@ defmodule Mix.Tasks.Step7Quote do
end)
case args do
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
[] -> Mal.Env.set(env, "*ARGV*", [])
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", list(rest))
[] -> Mal.Env.set(env, "*ARGV*", list([]))
end
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -74,7 +80,6 @@ defmodule Mix.Tasks.Step7Quote do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -83,18 +88,34 @@ defmodule Mix.Tasks.Step7Quote do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
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)]
defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])])
defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg
defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "concat"}, first, right])
end
defp quasiquote([head | tail], env) do
[{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
defp quasi_list([head | tail], env) do
left = quasiquote(head, env)
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "cons"}, left, right])
end
defp eval([{:symbol, "if"}, condition, if_true | if_false], env) do
defp quasiquote({list_type, ast, _}, env)
when list_type in [:list, :vector] do
quasi_list(ast, env)
end
defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast])
defp eval({:list, ast, meta}, env), do: eval_list(ast, env, meta)
defp eval(ast, env), do: eval_ast(ast, env)
defp 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
@ -106,27 +127,29 @@ defmodule Mix.Tasks.Step7Quote do
end
end
defp eval([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval([{:symbol, "fn*"}, params, body], env)
end
defp eval([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -134,26 +157,24 @@ defmodule Mix.Tasks.Step7Quote do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval([{:symbol, "quote"}, arg], _env), do: arg
defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
defp eval([{:symbol, "quasiquote"}, ast], env) do
defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
quasiquote(ast, env)
|> eval(env)
end
defp eval(ast, env) when is_list(ast) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
{:closure, closure} -> closure.(args)
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp print(value) do
Mal.Printer.print_str(value)
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step8Macros do
import Mal.Types
alias Mal.Function
def run(args) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -57,37 +60,40 @@ defmodule Mix.Tasks.Step8Macros do
end)
case args do
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
[] -> Mal.Env.set(env, "*ARGV*", [])
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", list(rest))
[] -> Mal.Env.set(env, "*ARGV*", list([]))
end
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
case Mal.Env.get(env, symbol) do
{:ok, value} -> value
:not_found -> throw({:error, "invalid symbol #{symbol}"})
:not_found -> throw({:error, "'#{symbol}' not found"})
end
end
@ -97,7 +103,6 @@ defmodule Mix.Tasks.Step8Macros do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -106,27 +111,40 @@ defmodule Mix.Tasks.Step8Macros do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
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)]
defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])])
defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg
defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "concat"}, first, right])
end
defp quasiquote([head | tail], env) do
[{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
defp quasi_list([head | tail], env) do
left = quasiquote(head, env)
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "cons"}, left, right])
end
defp macro_call?([{:symbol, key} | _tail], env) do
defp quasiquote({list_type, ast, _}, env)
when list_type in [:list, :vector] do
quasi_list(ast, env)
end
defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast])
defp macro_call?({:list, [{:symbol, key} | _tail], _}, env) do
case Mal.Env.get(env, key) do
{:ok, {:macro, _}} -> true
{:ok, %Function{value: macro, macro: true}} -> 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)
defp do_macro_call({:list, [{:symbol, key} | tail], _}, env) do
{:ok, %Function{value: macro, macro: true}} = Mal.Env.get(env, key)
macro.(tail)
|> macroexpand(env)
end
@ -139,17 +157,17 @@ defmodule Mix.Tasks.Step8Macros do
end
end
defp eval(ast, env) when not is_list(ast), do: eval_ast(ast, env)
defp eval(ast, env) when is_list(ast) do
defp eval({:list, _list, _meta} = ast, env) do
case macroexpand(ast, env) do
result when is_list(result) -> eval_list(result, env)
{:list, list, meta} -> eval_list(list, env, meta)
result -> result
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp eval_list([{:symbol, "macroexpand"}, ast], env), do: macroexpand(ast, env)
defp eval_list([{:symbol, "macroexpand"}, ast], env, _), do: macroexpand(ast, env)
defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env) do
defp 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
@ -161,34 +179,35 @@ defmodule Mix.Tasks.Step8Macros do
end
end
defp eval_list([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env) do
{:closure, evaluated} = eval(function, env)
macro = {:macro, evaluated}
defp eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env, _) do
macro = %{eval(function, env) | macro: true}
Mal.Env.set(env, key, macro)
macro
end
defp eval_list([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval_list([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval_list([{:symbol, "fn*"}, params, body], env)
end
defp eval_list([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -196,20 +215,20 @@ defmodule Mix.Tasks.Step8Macros do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval_list([{:symbol, "quote"}, arg], _env), do: arg
defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
defp eval_list([{:symbol, "quasiquote"}, ast], env) do
defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
quasiquote(ast, env)
|> eval(env)
end
defp eval_list(ast, env) do
[func | args] = eval_ast(ast, env)
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
{:closure, closure} -> closure.(args)
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end

View File

@ -1,4 +1,7 @@
defmodule Mix.Tasks.Step9Try do
import Mal.Types
alias Mal.Function
def run(args) do
env = Mal.Env.initialize()
Mal.Env.merge(env, Mal.Core.namespace)
@ -57,31 +60,34 @@ defmodule Mix.Tasks.Step9Try do
end)
case args do
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", rest)
[] -> Mal.Env.set(env, "*ARGV*", [])
[_file_name | rest] -> Mal.Env.set(env, "*ARGV*", list(rest))
[] -> Mal.Env.set(env, "*ARGV*", list([]))
end
end
defp loop(env) do
Mal.Core.readline("user> ")
IO.write(:stdio, "user> ")
IO.read(:stdio, :line)
|> read_eval_print(env)
|> IO.puts
loop(env)
end
defp eval_ast(ast, env) when is_list(ast) do
Enum.map(ast, fn elem -> eval(elem, env) end)
defp eval_ast({:list, ast, meta}, env) when is_list(ast) do
{:list, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast(ast, env) when is_map(ast) do
for {key, value} <- ast, into: %{} do
defp eval_ast({:map, ast, meta}, env) do
map = for {key, value} <- ast, into: %{} do
{eval(key, env), eval(value, env)}
end
{:map, map, meta}
end
defp eval_ast({:vector, ast}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end)}
defp eval_ast({:vector, ast, meta}, env) do
{:vector, Enum.map(ast, fn elem -> eval(elem, env) end), meta}
end
defp eval_ast({:symbol, symbol}, env) do
@ -97,7 +103,6 @@ defmodule Mix.Tasks.Step9Try do
Mal.Reader.read_str(input)
end
defp eval_bindings({:vector, vector}, env), do: eval_bindings(vector, env)
defp eval_bindings([], _env), do: _env
defp eval_bindings([{:symbol, key}, binding | tail], env) do
evaluated = eval(binding, env)
@ -106,27 +111,40 @@ defmodule Mix.Tasks.Step9Try do
end
defp eval_bindings(_bindings, _env), do: throw({:error, "Unbalanced let* bindings"})
defp quasiquote({:vector, list}, _env), do: quasiquote(list, _env)
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)]
defp quasi_list([], _env), do: list([{:symbol, "quote"}, list([])])
defp quasi_list([{:symbol, "unquote"}, arg], _env), do: arg
defp quasi_list([{:list, [{:symbol, "splice-unquote"}, first], _meta} | tail], env) do
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "concat"}, first, right])
end
defp quasiquote([head | tail], env) do
[{:symbol, "cons"}, quasiquote(head, env), quasiquote(tail, env)]
defp quasi_list([head | tail], env) do
left = quasiquote(head, env)
right = tail
|> list
|> quasiquote(env)
list([{:symbol, "cons"}, left, right])
end
defp macro_call?([{:symbol, key} | _tail], env) do
defp quasiquote({list_type, ast, _}, env)
when list_type in [:list, :vector] do
quasi_list(ast, env)
end
defp quasiquote(ast, _env), do: list([{:symbol, "quote"}, ast])
defp macro_call?({:list, [{:symbol, key} | _tail], _}, env) do
case Mal.Env.get(env, key) do
{:ok, {:macro, _}} -> true
{:ok, %Function{value: macro, macro: true}} -> 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)
defp do_macro_call({:list, [{:symbol, key} | tail], _}, env) do
{:ok, %Function{value: macro, macro: true}} = Mal.Env.get(env, key)
macro.(tail)
|> macroexpand(env)
end
@ -139,17 +157,17 @@ defmodule Mix.Tasks.Step9Try do
end
end
defp eval(ast, env) when not is_list(ast), do: eval_ast(ast, env)
defp eval(ast, env) when is_list(ast) do
defp eval({:list, _list, _meta} = ast, env) do
case macroexpand(ast, env) do
result when is_list(result) -> eval_list(result, env)
{:list, list, meta} -> eval_list(list, env, meta)
result -> result
end
end
defp eval(ast, env), do: eval_ast(ast, env)
defp eval_list([{:symbol, "macroexpand"}, ast], env), do: macroexpand(ast, env)
defp eval_list([{:symbol, "macroexpand"}, ast], env, _), do: macroexpand(ast, env)
defp eval_list([{:symbol, "if"}, condition, if_true | if_false], env) do
defp 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
@ -161,34 +179,35 @@ defmodule Mix.Tasks.Step9Try do
end
end
defp eval_list([{:symbol, "do"} | ast], env) do
eval_ast(List.delete_at(ast, -1), env)
defp eval_list([{:symbol, "do"} | ast], env, _) do
ast
|> List.delete_at(-1)
|> list
|> eval_ast(env)
eval(List.last(ast), env)
end
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env) do
defp eval_list([{:symbol, "def!"}, {:symbol, key}, value], env, _) do
evaluated = eval(value, env)
Mal.Env.set(env, key, evaluated)
evaluated
end
defp eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env) do
{:closure, evaluated} = eval(function, env)
macro = {:macro, evaluated}
defp eval_list([{:symbol, "defmacro!"}, {:symbol, key}, function], env, _) do
macro = %{eval(function, env) | macro: true}
Mal.Env.set(env, key, macro)
macro
end
defp eval_list([{:symbol, "let*"}, bindings, body], env) do
defp eval_list([{:symbol, "let*"}, {list_type, bindings, _}, body], env, _)
when list_type == :list or list_type == :vector do
let_env = Mal.Env.initialize(env)
eval_bindings(bindings, let_env)
eval(body, let_env)
end
defp eval_list([{:symbol, "fn*"}, {:vector, params}, body], env) do
eval_list([{:symbol, "fn*"}, params, body], env)
end
defp eval_list([{:symbol, "fn*"}, params, body], env) do
defp eval_list([{:symbol, "fn*"}, {list_type, params, _}, body], env, _)
when list_type == :list or list_type == :vector do
param_symbols = for {:symbol, symbol} <- params, do: symbol
closure = fn args ->
@ -196,19 +215,34 @@ defmodule Mix.Tasks.Step9Try do
eval(body, inner)
end
{:closure, closure}
%Function{value: closure}
end
defp eval_list([{:symbol, "quote"}, arg], _env), do: arg
defp eval_list([{:symbol, "quote"}, arg], _env, _), do: arg
defp eval_list([{:symbol, "quasiquote"}, ast], env) do
defp eval_list([{:symbol, "quasiquote"}, ast], env, _) do
quasiquote(ast, env)
|> eval(env)
end
# (try* A (catch* B C))
defp eval_list([{:symbol, "try*"}, try_form,
[{:symbol, "catch*"}, {:symbol, exception}, catch_form]], env) do
defp eval_list([{:symbol, "try*"}, try_form, {:list, catch_list, _meta}], env, _) do
eval_try(try_form, catch_list, env)
end
defp eval_list([{:symbol, "try*"}, _try_form, _], _env, _) do
throw({:error, "try* requires a list as the second parameter"})
end
defp eval_list(ast, env, meta) do
{:list, [func | args], _} = eval_ast({:list, ast, meta}, env)
case func do
%Function{value: closure} -> closure.(args)
_ -> func.(args)
end
end
defp eval_try(try_form,
[{:symbol, "catch*"}, {:symbol, exception}, catch_form], env) do
try do
eval(try_form, env)
catch
@ -218,13 +252,8 @@ defmodule Mix.Tasks.Step9Try do
eval(catch_form, catch_env)
end
end
defp eval_list(ast, env) do
[func | args] = eval_ast(ast, env)
case func do
{:closure, closure} -> closure.(args)
_ -> func.(args)
end
defp eval_try(_try_form, _catch_list, _env) do
throw({:error, "catch* requires two arguments"})
end
defp print(value) do