From c5b9822b4456996ced5c134070b7e4a0716a22fb Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Wed, 24 Apr 2024 11:32:49 +0200 Subject: [PATCH] Rewrites all Ast usage to use Tuples --- ovo/lib/ovo.ex | 2 +- ovo/lib/ovo/ast.ex | 17 +- ovo/lib/ovo/builtins.ex | 76 ++++----- ovo/lib/ovo/converter.ex | 18 +-- ovo/lib/ovo/interpreter.ex | 245 +++++++++++++++-------------- ovo/lib/ovo/parser.ex | 6 +- ovo/lib/ovo/printer.ex | 51 +++--- ovo/lib/ovo/registry.ex | 12 +- ovo/lib/ovo/rewrites.ex | 176 ++++++++++----------- ovo/lib/ovo/runner.ex | 2 +- ovo/test/ovo_distribution_test.exs | 25 ++- ovo/test/ovo_shake_test.exs | 6 +- ovo/test/ovo_test.exs | 66 ++++---- 13 files changed, 359 insertions(+), 343 deletions(-) diff --git a/ovo/lib/ovo.ex b/ovo/lib/ovo.ex index 38ed034..6adf023 100644 --- a/ovo/lib/ovo.ex +++ b/ovo/lib/ovo.ex @@ -41,7 +41,7 @@ defmodule Ovo do Runs Ovo code through the interpreter iex> Ovo.run("addone = \\a -> add(1, a) end addone(2)") - iex> {%Ovo.Ast{kind: :integer, nodes: [], value: 3}} + iex> {{:integer, [], 3}} """ def run(code, input \\ %{}), do: Ovo.Interpreter.run(code, input) diff --git a/ovo/lib/ovo/ast.ex b/ovo/lib/ovo/ast.ex index 9a9478a..f282eb5 100644 --- a/ovo/lib/ovo/ast.ex +++ b/ovo/lib/ovo/ast.ex @@ -29,19 +29,14 @@ defmodule Ovo.Ast do @typedoc """ An Ast node. """ - @type t :: %__MODULE__{ - kind: kind(), - nodes: list(t()), - value: term() - } - defstruct [:kind, :nodes, :value] + @type t :: {kind(), list(t()), term()} @doc """ Helper to instantiate Ast Nodes. """ @spec make(kind(), term(), list(t())) :: t() def make(kind, value, children), - do: %__MODULE__{kind: kind, nodes: children, value: value} + do: {kind, children, value} @doc """ Instantiates a root node. @@ -102,21 +97,21 @@ defmodule Ovo.Ast do Currently used for parenthesized expressions, but will certainly be refactored out later. """ @spec expr(t() | list(t())) :: t() - def expr([val]) when is_struct(val, Ast), do: make(:expr, val, []) - def expr(val) when is_struct(val, Ast), do: val + def expr([val]) when is_tuple(val), do: make(:expr, val, []) + def expr(val) when is_tuple(val), do: val @doc """ Instantiates an assignment node, where symbol must be a symbol node and expr an Ast node. """ @spec assignment(t(), t()) :: t() - def assignment(symbol, expr) when is_struct(symbol, Ast) and is_struct(expr, Ast), + def assignment(symbol, expr) when is_tuple(symbol) and is_tuple(expr), do: make(:assignment, symbol, expr) @doc """ Instantiates a shakable lambda node, where lambda must be a Lambda ast node. """ @spec shake(t()) :: t() - def shake(lambda) when is_struct(lambda, Ast), do: make(:shake, lambda, []) + def shake(lambda) when is_tuple(lambda), do: make(:shake, lambda, []) @doc """ Instantiates a block node. Will probably be removed. diff --git a/ovo/lib/ovo/builtins.ex b/ovo/lib/ovo/builtins.ex index 1ff032c..fb0efc7 100644 --- a/ovo/lib/ovo/builtins.ex +++ b/ovo/lib/ovo/builtins.ex @@ -51,7 +51,7 @@ defmodule Ovo.Builtins do defp hex(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :integer, value: v}] -> + [{:integer, _, v}] -> Ovo.Ast.string(Integer.to_string(v, 16)) _ -> @@ -63,7 +63,7 @@ defmodule Ovo.Builtins do import Bitwise case map_nodes(nodes, env) do - [%{kind: :integer, value: v}] -> + [{:integer, _, v}] -> Ovo.Ast.integer(v &&& 0xFFFFFFFF) _ -> @@ -75,7 +75,7 @@ defmodule Ovo.Builtins do import Bitwise case map_nodes(nodes, env) do - [%{kind: :integer, value: v}, %{kind: :integer, value: v1}] -> + [{:integer, _, v}, {:integer, _, v1}] -> Ovo.Ast.integer(v <<< v1) _ -> @@ -87,7 +87,7 @@ defmodule Ovo.Builtins do import Bitwise case map_nodes(nodes, env) do - [%{kind: :integer, value: v}, %{kind: :integer, value: v1}] -> + [{:integer, _, v}, {:integer, _, v1}] -> Ovo.Ast.integer(v >>> v1) _ -> @@ -99,7 +99,7 @@ defmodule Ovo.Builtins do import Bitwise case map_nodes(nodes, env) do - [%{kind: :integer, value: v}, %{kind: :integer, value: v1}] -> + [{:integer, _, v}, {:integer, _, v1}] -> Ovo.Ast.integer(bxor(v, v1)) _ -> @@ -109,7 +109,7 @@ defmodule Ovo.Builtins do defp intval(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: v}] -> + [{:string, _, v}] -> <> = v Ovo.Ast.integer(k) @@ -120,14 +120,14 @@ defmodule Ovo.Builtins do defp o_len(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: v}] -> Ovo.Ast.integer(String.length(v)) + [{:string, _, v}] -> Ovo.Ast.integer(String.length(v)) _ -> :error end end defp at(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: v}, %{kind: :integer, value: v1}] -> + [{:string, _, v}, {:integer, _, v1}] -> Ovo.Ast.string(String.at(v, v1)) _ -> @@ -137,10 +137,10 @@ defmodule Ovo.Builtins do defp concat(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: v}, %{kind: :string, value: v2}] -> + [{:string, _, v}, {:string, _, v2}] -> Ovo.Ast.string("#{v}#{v2}") - [%{kind: :list, nodes: n1}, %{kind: :list, nodes: n2}] -> + [{:list, n1, _}, {:list, n2, _}] -> Ovo.Ast.list(n1 ++ n2) _ -> @@ -150,7 +150,7 @@ defmodule Ovo.Builtins do defp to_string(nodes, env) do case map_nodes(nodes, env) do - [%{kind: k, value: v}] when k in [:string, :float, :integer] -> + [{k, _, v}] when k in [:string, :float, :integer] -> Ovo.Ast.string("#{v}") _ -> @@ -167,7 +167,7 @@ defmodule Ovo.Builtins do defp invoke(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: hash}, %{kind: :list, nodes: ns}] -> + [{:string, _, hash}, {:list, ns, _}] -> {h, host} = case String.split(hash, "@") do [h] -> {h, Node.self()} @@ -193,9 +193,9 @@ defmodule Ovo.Builtins do defp arg(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :integer, value: v}] -> - data = Ovo.Env.find_value("data", env) - Map.get(data.value, "arg#{v}") + [{:integer, _, v}] -> + {_, _, data} = Ovo.Env.find_value("data", env) + Map.get(data, "arg#{v}") _ -> :error @@ -204,9 +204,9 @@ defmodule Ovo.Builtins do defp access(nodes, env) do case map_nodes(nodes, env) do - [%{kind: :string, value: v}] -> - data = Ovo.Env.find_value("data", env) - Map.get(data.value, v) + [{:string, _, v}] -> + {_, _, data} = Ovo.Env.find_value("data", env) + Map.get(data, v) _ -> :error @@ -215,7 +215,7 @@ defmodule Ovo.Builtins do defp compare(nodes, env, operator) do case map_nodes(nodes, env) do - [%{kind: k1, value: v1}, %{kind: k2, value: v2}] + [{k1, _, v1}, {k2, _, v2}] when k1 in [:float, :integer] and k2 in [:float, :integer] -> Ovo.Ast.bool(operator.(v1, v2)) @@ -230,7 +230,7 @@ defmodule Ovo.Builtins do def strictly_smaller(nodes, env), do: compare(nodes, env, &Kernel. + [{:string, _, v}] -> Ovo.Runner.shake(v) _ -> @@ -270,7 +270,7 @@ defmodule Ovo.Builtins do defp map(nodes, env) do case map_nodes(nodes, env) do - [fun, %Ast{kind: :list, nodes: items}] when is_function(fun) -> + [fun, {:list, items, _}] when is_function(fun) -> Ovo.Ast.list(Enum.map(items, fn i -> fun.([i]) end)) _ -> @@ -280,7 +280,7 @@ defmodule Ovo.Builtins do defp filter(nodes, env) do case map_nodes(nodes, env) do - [fun, %Ast{kind: :list, nodes: items}] when is_function(fun) -> + [fun, {:list, items, _}] when is_function(fun) -> Ovo.Ast.list(Enum.filter(items, fn i -> fun.([i]) end)) _ -> @@ -290,7 +290,7 @@ defmodule Ovo.Builtins do defp reduce(nodes, env) do case map_nodes(nodes, env) do - [fun, %Ast{kind: :list, nodes: items}, %Ast{} = initial_value] when is_function(fun) -> + [fun, {:list, items, _}, {} = initial_value] when is_function(fun) -> Enum.reduce(items, initial_value, fn i, acc -> fun.([acc, i]) end) @@ -302,13 +302,13 @@ defmodule Ovo.Builtins do defp add(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :integer, value: v1}, %Ast{kind: :integer, value: v2}] -> + [{:integer, _, v1}, {:integer, _, v2}] -> Ast.integer(v1 + v2) - [%Ast{kind: :float, value: v1}, %Ast{kind: :float, value: v2}] -> + [{:float, _, v1}, {:float, _, v2}] -> Ast.float(v1 + v2) - [%Ast{kind: k1, value: v1}, %Ast{kind: k2, value: v2}] + [{k1, _, v1}, {k2, _, v2}] when k1 in [:integer, :float] and k2 in [:integer, :float] -> Ast.float(v1 + v2) @@ -319,13 +319,13 @@ defmodule Ovo.Builtins do defp subtract(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :integer, value: v1}, %Ast{kind: :integer, value: v2}] -> + [{:integer, _, v1}, {:integer, _, v2}] -> Ast.integer(v1 - v2) - [%Ast{kind: :float, value: v1}, %Ast{kind: :float, value: v2}] -> + [{:float, _, v1}, {:float, _, v2}] -> Ast.float(v1 - v2) - [%Ast{kind: k1, value: v1}, %Ast{kind: k2, value: v2}] + [{k1, _, v1}, {k2, _, v2}] when k1 in [:integer, :float] and k2 in [:integer, :float] -> Ast.float(v1 - v2) @@ -336,13 +336,13 @@ defmodule Ovo.Builtins do defp multiply(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :integer, value: v1}, %Ast{kind: :integer, value: v2}] -> + [{:integer, _, v1}, {:integer, _, v2}] -> Ast.integer(v1 * v2) - [%Ast{kind: :float, value: v1}, %Ast{kind: :float, value: v2}] -> + [{:float, _, v1}, {:float, _, v2}] -> Ast.float(v1 * v2) - [%Ast{kind: k1, value: v1}, %Ast{kind: k2, value: v2}] + [{k1, _, v1}, {k2, _, v2}] when k1 in [:integer, :float] and k2 in [:integer, :float] -> Ast.float(v1 * v2) @@ -353,13 +353,13 @@ defmodule Ovo.Builtins do defp divide(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :integer, value: v1}, %Ast{kind: :integer, value: v2}] -> + [{:integer, _, v1}, {:integer, _, v2}] -> Ast.integer(v1 / v2) - [%Ast{kind: :float, value: v1}, %Ast{kind: :float, value: v2}] -> + [{:float, _, v1}, {:float, _, v2}] -> Ast.float(v1 / v2) - [%Ast{kind: k1, value: v1}, %Ast{kind: k2, value: v2}] + [{k1, _, v1}, {k2, _, v2}] when k1 in [:integer, :float] and k2 in [:integer, :float] -> Ast.float(v1 / v2) @@ -370,14 +370,14 @@ defmodule Ovo.Builtins do defp map_access(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :map, value: v}, %Ast{kind: :string, value: v2}] -> Map.get(v, v2) + [{:map, _, v}, {:string, _, v2}] -> Map.get(v, v2) _ -> :error end end defp map_set(nodes, env) do case map_nodes(nodes, env) do - [%Ast{kind: :map, value: v}, %Ast{kind: :string, value: v2}, %Ast{} = v3] -> + [{:map, _, v}, {:string, _, v2}, {} = v3] -> Map.put(v, v2, v3) _ -> diff --git a/ovo/lib/ovo/converter.ex b/ovo/lib/ovo/converter.ex index aac389f..d296456 100644 --- a/ovo/lib/ovo/converter.ex +++ b/ovo/lib/ovo/converter.ex @@ -5,7 +5,6 @@ defmodule Ovo.Converter do alias Ovo.Ast @spec elixir_to_ovo(term()) :: Ovo.Ast.t() - def elixir_to_ovo(%Ast{} = term), do: term def elixir_to_ovo(term) when is_integer(term), do: Ast.integer(term) def elixir_to_ovo(term) when is_number(term), do: Ast.float(term) def elixir_to_ovo(term) when is_number(term), do: Ast.float(term) @@ -16,16 +15,17 @@ defmodule Ovo.Converter do def elixir_to_ovo(term) when is_map(term), do: Ast.map(Enum.map(term, fn {k, v} -> {k, elixir_to_ovo(v)} end) |> Enum.into(%{})) + def elixir_to_ovo(term), do: term @spec elixir_to_ovo(Ovo.Ast.t()) :: term() - def ovo_to_elixir(%Ast{kind: :bool, value: v}), do: v - def ovo_to_elixir(%Ast{kind: :float, value: v}), do: v - def ovo_to_elixir(%Ast{kind: :integer, value: v}), do: v - def ovo_to_elixir(%Ast{kind: :string, value: v}), do: v - def ovo_to_elixir(%Ast{kind: :symbol, value: v}), do: v - def ovo_to_elixir(%Ast{kind: :expr, value: v}), do: ovo_to_elixir(v) + def ovo_to_elixir({:bool, _, v}), do: v + def ovo_to_elixir({:float, _, v}), do: v + def ovo_to_elixir({:integer, _, v}), do: v + def ovo_to_elixir({:string, _, v}), do: v + def ovo_to_elixir({:symbol, _, v}), do: v + def ovo_to_elixir({:expr, _, v}), do: ovo_to_elixir(v) - def ovo_to_elixir(%Ast{kind: :map, value: v}), + def ovo_to_elixir({:map, _, v}), do: v |> Enum.map(fn {k, v1} -> {k, ovo_to_elixir(v1)} end) |> Enum.into(%{}) - def ovo_to_elixir(%Ast{kind: :list, nodes: n}), do: n |> Enum.map(&ovo_to_elixir/1) + def ovo_to_elixir({:list, n, _}), do: n |> Enum.map(&ovo_to_elixir/1) end diff --git a/ovo/lib/ovo/interpreter.ex b/ovo/lib/ovo/interpreter.ex index b5ee73d..37ea3e0 100644 --- a/ovo/lib/ovo/interpreter.ex +++ b/ovo/lib/ovo/interpreter.ex @@ -41,7 +41,7 @@ defmodule Ovo.Interpreter do run(ast, input) end - def run(%Ast{} = ast, input) do + def run(ast, input) do {:ok, evaluator_pid} = start_link([]) ovo_input = Ovo.Converter.elixir_to_ovo(input) @@ -73,138 +73,145 @@ defmodule Ovo.Interpreter do @spec evaluate(list(Ovo.Ast.t()) | Ovo.Ast.t(), pid()) :: {Ovo.Ast.t(), map()} def evaluate(nodes, env) when is_list(nodes), do: reduce_nodes(nodes, env) - def evaluate(%Ovo.Ast{} = ast, env) do - res = case ast.kind do - :root -> - evaluate(ast.nodes, env) + def evaluate(ast, env) when is_tuple(ast) and tuple_size(ast) == 3 do + {a_kind, a_nodes, a_value} = ast - :assignment -> - key = ast.value - {_, val} = evaluate(ast.nodes, env) - {Env.update_env(env, key.value, val), val} + res = + case a_kind do + :root -> + evaluate(a_nodes, env) - :block -> - evaluate(ast.nodes, env) + :assignment -> + key = a_value + {_, _, assi_v} = key + {_, val} = evaluate(a_nodes, env) + {Env.update_env(env, assi_v, val), val} - :condition -> - [predicate, branch1, branch2] = ast.nodes - {_, val} = evaluate(predicate, env) + :block -> + evaluate(a_nodes, env) - {_, v} = - case val do - %Ast{kind: :bool, value: true} -> evaluate(branch1, env) - %Ast{kind: :bool, value: false} -> evaluate(branch2, env) - end + :condition -> + [predicate, branch1, branch2] = a_nodes + {_, val} = evaluate(predicate, env) + + {_, v} = + case val do + {:bool, _, true} -> evaluate(branch1, env) + {:bool, _, false} -> evaluate(branch2, env) + end + + {env, v} - {env, v} - - :shake -> - {_env, inner_fn} = evaluate(ast.value, env) - key = :crypto.strong_rand_bytes(16) |> Base.encode64() |> String.slice(0..16) - - {env, - %{ - callable: fn args -> - res = inner_fn.(args) - - Agent.update(env, fn state -> - shake_stack = Map.get(state.shakes, key, []) - out = put_in(state, [:shakes, key], [res | shake_stack]) - out - end) - - res - end, - key: key - }} - - :lambda -> - arity = length(ast.value) - program = Ovo.Rewrites.rw(ast.nodes) - user_bindings = Env.user_bindings(env) - - {env, - fn args -> - {:ok, captured_env} = Env.fork(env) - - Env.update_captures(env, user_bindings) - - if length(args) != arity do - {:error, "#{length(args)} argument(s) passed instead of #{arity}"} - else - symbols_and_args = Enum.zip(ast.value, args) - - env_with_applied_args = - Enum.reduce(symbols_and_args, captured_env, fn {sym, arg}, uenv -> - {_, v} = evaluate(arg, uenv) - Env.update_env(uenv, sym.value, v) - uenv + :shake -> + {_env, inner_fn} = evaluate(a_value, env) + key = :crypto.strong_rand_bytes(16) |> Base.encode64() |> String.slice(0..16) + + {env, + %{ + callable: fn args -> + res = inner_fn.(args) + + Agent.update(env, fn state -> + shake_stack = Map.get(state.shakes, key, []) + out = put_in(state, [:shakes, key], [res | shake_stack]) + out end) - {_, k} = evaluate(program, env_with_applied_args) - k - end - end} - - :call -> - case Env.find_callable(ast.value.value, env) do - {:user, %{callable: fun}} -> - evaluated_args = - ast.nodes - |> Enum.map(fn node -> - {_, v} = evaluate(node, env) - v - end) - - v = fun.(evaluated_args) - {env, v} - - {:user, fun} -> - evaluated_args = - ast.nodes - |> Enum.map(fn node -> - {_, v} = evaluate(node, env) - v - end) - - v = fun.(evaluated_args) - {env, v} - - {:builtins, fun} -> - r = fun.(ast.nodes, env) - {env, r} - - {:error, msg} -> - throw({:error, msg}) - end - - :symbol -> - {env, Env.find_value(ast.value, env)} - - :expr -> - evaluate(ast.value, env) - - :list -> - {env, - %Ast{ - kind: :list, - value: nil, - nodes: - Enum.map(ast.nodes, fn n -> + res + end, + key: key + }} + + :lambda -> + arity = length(a_value) + program = Ovo.Rewrites.rw(a_nodes) + user_bindings = Env.user_bindings(env) + + {env, + fn args -> + {:ok, captured_env} = Env.fork(env) + + Env.update_captures(env, user_bindings) + + if length(args) != arity do + {:error, "#{length(args)} argument(s) passed instead of #{arity}"} + else + symbols_and_args = Enum.zip(a_value, args) + + env_with_applied_args = + Enum.reduce(symbols_and_args, captured_env, fn {sym, arg}, uenv -> + {_, _, sv} = sym + {_, v} = evaluate(arg, uenv) + Env.update_env(uenv, sv, v) + uenv + end) + + {_, k} = evaluate(program, env_with_applied_args) + k + end + end} + + :call -> + {_, _, nv} = a_value + + case Env.find_callable(nv, env) do + {:user, %{callable: fun}} -> + evaluated_args = + a_nodes + |> Enum.map(fn node -> + {_, v} = evaluate(node, env) + v + end) + + v = fun.(evaluated_args) + {env, v} + + {:user, fun} -> + evaluated_args = + a_nodes + |> Enum.map(fn node -> + {_, v} = evaluate(node, env) + v + end) + + v = fun.(evaluated_args) + {env, v} + + {:builtins, fun} -> + r = fun.(a_nodes, env) + {env, r} + + {:error, msg} -> + throw({:error, msg}) + end + + :symbol -> + {env, Env.find_value(a_value, env)} + + :expr -> + evaluate(a_value, env) + + :list -> + {env, + { + :list, + Enum.map(a_nodes, fn n -> {_, r} = evaluate(n, env) r - end) - }} + end), + nil + }} - _ -> - {env, ast} - end + _ -> + {env, ast} + end case res do {_, :error} -> - throw [ast, env] + throw([ast, env]) + _ -> - res + res end end end diff --git a/ovo/lib/ovo/parser.ex b/ovo/lib/ovo/parser.ex index db061d8..29086ec 100644 --- a/ovo/lib/ovo/parser.ex +++ b/ovo/lib/ovo/parser.ex @@ -97,9 +97,9 @@ defmodule Ovo.Parser do Parses a primitive value. iex> Ovo.Parser.p_value([{:number, "5"}]) - {:ok, %Ovo.Ast{kind: :integer, nodes: [], value: 5}, []} + {:ok, {:integer, [], 5}, []} iex> Ovo.Parser.p_value([{:string, "foo"}]) - {:ok, %Ovo.Ast{kind: :string, nodes: [], value: "foo"}, []} + {:ok, {:string, [], "foo"}, []} iex> Ovo.Parser.p_value([{:arrow, nil}]) {:error, [], [{:arrow, nil}]} """ @@ -178,7 +178,7 @@ defmodule Ovo.Parser do def p_argless_call(tokens) do case C.all([&p_symbol/1, C.match(:open_paren), C.match(:close_paren)]).(tokens) do - {:ok, [%Ast{kind: :symbol} = a | _], rest} -> {:ok, Ast.call(a), rest} + {:ok, [{:symbol, _, _} = a | _], rest} -> {:ok, Ast.call(a), rest} b -> b end end diff --git a/ovo/lib/ovo/printer.ex b/ovo/lib/ovo/printer.ex index f1a2e87..42d206d 100644 --- a/ovo/lib/ovo/printer.ex +++ b/ovo/lib/ovo/printer.ex @@ -2,46 +2,53 @@ defmodule Ovo.Printer do @moduledoc """ Prints an Ovo.Ast to Ovo code. Note that non-significant symbols like parentheses in parenthesized expressions are discarded (until the eventual introductions of operators and precedence problems). """ - def print(%Ovo.Ast{} = ast) do - Enum.reduce(ast.nodes, "", fn node, output -> - output <> print_node(node) <> "\n" - end) - end - def print({:ok, %Ovo.Ast{} = tree, []}) do + def print({:ok, tree, []}) do print(tree) end - def print_node(%Ovo.Ast{kind: :expr, value: val}), do: "(#{print_node(val)})" + def print(ast) when is_tuple(ast) and tuple_size(ast) == 3 do + {_, nodes, _} = ast - def print_node(%Ovo.Ast{kind: :call, value: val, nodes: children}) do - "#{val.value}(#{Enum.map_join(children, ", ", &print_node/1)})" + Enum.reduce(nodes, "", fn node, output -> + output <> print_node(node) <> "\n" + end) end - def print_node(%Ovo.Ast{kind: :infix, value: symbol, nodes: [e1, e2]}) do + def print_node({:expr, _, val}), do: "(#{print_node(val)})" + + def print_node({:call, children, val}) do + {_, _, vv} = val + "#{vv}(#{Enum.map_join(children, ", ", &print_node/1)})" + end + + def print_node({:infix, [e1, e2], symbol}) do s = Ovo.Infix.token_to_text(symbol) "#{print_node(e1)} #{s} #{print_node(e2)}" end - def print_node(%Ovo.Ast{kind: :assignment, value: sym, nodes: expr}) do - "#{sym.value} = #{print_node(expr)}" + def print_node({:assignment, expr, sym}) do + case sym do + {_, _, {_, _, vv}} -> "#{vv} = #{print_node(expr)}" + {_, _, vv} -> "#{vv} = #{print_node(expr)}" + end end - def print_node(%Ovo.Ast{kind: :shake, value: v}) do + def print_node({:shake, _, v}) do "!#{print_node(v)}" end - def print_node(%Ovo.Ast{kind: :bool, value: s}), do: if(s, do: "T", else: "F") + def print_node({:bool, _, s}), do: if(s, do: "T", else: "F") - def print_node(%Ovo.Ast{kind: :symbol, value: val}) do + def print_node({:symbol, _, val}) do "#{val}" end - def print_node(%Ovo.Ast{kind: :integer, value: val}) do + def print_node({:integer, _, val}) do "#{val}" end - def print_node(%Ovo.Ast{kind: :lambda, value: head, nodes: body}) do + def print_node({:lambda, body, head}) do """ \\#{Enum.map_join(head, ", ", &print_node/1)} -> #{print_node(body)} @@ -49,15 +56,15 @@ defmodule Ovo.Printer do """ end - def print_node(%Ovo.Ast{kind: :float, value: val}) do + def print_node({:float, _, val}) do "#{val}" end - def print_node(%Ovo.Ast{kind: :string, value: val}) do + def print_node({:string, _, val}) do "`#{val |> String.replace("`", "\\`")}`" end - def print_node(%Ovo.Ast{kind: :condition, nodes: [predicate, valid, invalid]}) do + def print_node({:condition, [predicate, valid, invalid], _}) do """ if #{print_node(predicate)} then #{print_node(valid)} @@ -67,11 +74,11 @@ defmodule Ovo.Printer do """ end - def print_node(%Ovo.Ast{kind: :list, nodes: nodes}) do + def print_node({:list, nodes, _}) do "[#{Enum.map_join(nodes, ", ", &print_node/1)}]" end - def print_node(%Ovo.Ast{kind: :block, nodes: nodes}) do + def print_node({:block, nodes, _}) do "#{Enum.map_join(nodes, "\n", &print_node/1)}" end diff --git a/ovo/lib/ovo/registry.ex b/ovo/lib/ovo/registry.ex index 693613a..790efab 100644 --- a/ovo/lib/ovo/registry.ex +++ b/ovo/lib/ovo/registry.ex @@ -62,8 +62,16 @@ defmodule Ovo.Registry do def wrap_registration(ast, code, name, hash, args) do Agent.update(__MODULE__, fn state -> - {:ok, pid} = Ovo.Runner.start_link(%Ovo.Runner{ast: ast, code: code, name: name, hash: hash}) - state |> Map.put(hash, %{runner: pid, stack: [], last_env: %{}, metadata: %{code: code, name: name, args: args}}) + {:ok, pid} = + Ovo.Runner.start_link(%Ovo.Runner{ast: ast, code: code, name: name, hash: hash}) + + state + |> Map.put(hash, %{ + runner: pid, + stack: [], + last_env: %{}, + metadata: %{code: code, name: name, args: args} + }) end) end diff --git a/ovo/lib/ovo/rewrites.ex b/ovo/lib/ovo/rewrites.ex index cbf296f..d17d8ba 100644 --- a/ovo/lib/ovo/rewrites.ex +++ b/ovo/lib/ovo/rewrites.ex @@ -1,133 +1,133 @@ defmodule Ovo.Rewrites do alias Ovo.Ast - def rewrite_node(%Ast{kind: :infix, value: op, nodes: [left, right]}) do - %Ast{ - kind: :call, - value: %Ovo.Ast{kind: :symbol, nodes: [], value: Ovo.Infix.infix_to_builtin(op)}, - nodes: [rw(left), rw(right)] + def rewrite_node({:infix, [left, right], op}) do + { + :call, + [rw(left), rw(right)], + {:symbol, [], Ovo.Infix.infix_to_builtin(op)} } end - def rewrite_node(%Ast{kind: k, value: v, nodes: n}), - do: %Ast{kind: k, value: rw(v), nodes: rw(n)} + def rewrite_node({k, v, n}), + do: {k, rw(v), rw(n)} def rewrite_node(b), do: b def rewrite_node_list([ - %Ovo.Ast{ - kind: :assignment, - nodes: %Ovo.Ast{ - kind: :call, - nodes: [ + { + :assignment, + { + :call, + [ maparg0, maparg1 ], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "map"} + {:symbol, [], "map"} }, - value: %Ovo.Ast{kind: :symbol, nodes: [], value: name1} + {:symbol, [], name1} }, - %Ovo.Ast{ - kind: :assignment, - nodes: %Ovo.Ast{ - kind: :call, - nodes: [ - %Ovo.Ast{kind: :symbol, nodes: [], value: name1}, + { + :assignment, + { + :call, + [ + {:symbol, [], name1}, filterarg1 ], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "filter"} + {:symbol, [], "filter"} }, - value: out + out } | rest ]) do [ - %Ovo.Ast{ - kind: :assignment, - value: out, - nodes: %Ovo.Ast{ - kind: :call, - nodes: [ - %Ovo.Ast{ - kind: :lambda, - nodes: %Ovo.Ast{ - kind: :block, - nodes: [ - %Ovo.Ast{ - kind: :assignment, - nodes: maparg1, - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "map_fn"} + { + :assignment, + out, + { + :call, + [ + { + :lambda, + { + :block, + [ + { + :assignment, + maparg1, + {:symbol, [], "map_fn"} }, - %Ovo.Ast{ - kind: :assignment, - nodes: filterarg1, - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "filter_fn"} + { + :assignment, + filterarg1, + {:symbol, [], "filter_fn"} }, - %Ovo.Ast{ - kind: :assignment, - nodes: %Ovo.Ast{ - kind: :call, - nodes: [%Ovo.Ast{kind: :symbol, nodes: [], value: "i"}], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "map_fn"} + { + :assignment, + { + :call, + [{:symbol, [], "i"}], + {:symbol, [], "map_fn"} }, - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "mapped"} + {:symbol, [], "mapped"} }, - %Ovo.Ast{ - kind: :condition, - nodes: [ - %Ovo.Ast{ - kind: :call, - nodes: [%Ovo.Ast{kind: :symbol, nodes: [], value: "mapped"}], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "filter_fn"} + { + :condition, + [ + { + :call, + [{:symbol, [], "mapped"}], + {:symbol, [], "filter_fn"} }, - %Ovo.Ast{ - kind: :block, - nodes: [ - %Ovo.Ast{ - kind: :call, - nodes: [ - %Ovo.Ast{kind: :symbol, nodes: [], value: "acc"}, - %Ovo.Ast{ - kind: :list, - nodes: [ - %Ovo.Ast{ - kind: :symbol, - nodes: [], - value: "mapped" + { + :block, + [ + { + :call, + [ + {:symbol, [], "acc"}, + { + :list, + [ + { + :symbol, + [], + "mapped" } ], - value: nil + nil } ], - value: %Ovo.Ast{ - kind: :symbol, - nodes: [], - value: "concat" + { + :symbol, + [], + "concat" } } ], - value: nil + nil }, - %Ovo.Ast{ - kind: :block, - nodes: [%Ovo.Ast{kind: :symbol, nodes: [], value: "acc"}], - value: nil + { + :block, + [{:symbol, [], "acc"}], + nil } ], - value: nil + nil } ], - value: nil + nil }, - value: [ - %Ovo.Ast{kind: :symbol, nodes: [], value: "acc"}, - %Ovo.Ast{kind: :symbol, nodes: [], value: "i"} + [ + {:symbol, [], "acc"}, + {:symbol, [], "i"} ] }, maparg0, - %Ovo.Ast{kind: :list, nodes: [], value: nil} + {:list, [], nil} ], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "reduce"} + {:symbol, [], "reduce"} } } | rewrite_node_list(rest) @@ -140,7 +140,7 @@ defmodule Ovo.Rewrites do def rw(a) when is_list(a), do: rewrite_node_list(a) def rw(a), do: rewrite_node(a) - def rewrite(%Ast{kind: k, value: v, nodes: nodes}) do - %Ast{kind: k, value: rw(v), nodes: rw(nodes)} + def rewrite({k, v, nodes}) do + {k, rw(v), rw(nodes)} end end diff --git a/ovo/lib/ovo/runner.ex b/ovo/lib/ovo/runner.ex index 77ab02d..ab576a9 100644 --- a/ovo/lib/ovo/runner.ex +++ b/ovo/lib/ovo/runner.ex @@ -29,7 +29,7 @@ defmodule Ovo.Runner do end end - def to_positional_args(%Ovo.Ast{kind: :list, nodes: n}), do: to_positional_args(n) + def to_positional_args({:list, n, _}), do: to_positional_args(n) def to_positional_args(inputs) when is_list(inputs) do Enum.with_index(inputs) diff --git a/ovo/test/ovo_distribution_test.exs b/ovo/test/ovo_distribution_test.exs index dbbe6ea..a9362f3 100644 --- a/ovo/test/ovo_distribution_test.exs +++ b/ovo/test/ovo_distribution_test.exs @@ -1,6 +1,5 @@ defmodule OvoDistributionTest do use ExUnit.Case, async: false - alias Ovo.Ast test "program linking" do Ovo.Registry.start_link(nil) @@ -21,19 +20,19 @@ defmodule OvoDistributionTest do assert hash2 == "1I9Jp" - assert %Ast{kind: :integer, value: 11, nodes: []} == + assert {:integer, [], 11} == Ovo.Registry.run_chain([hash2, hash1], [5]) - assert %Ast{kind: :integer, value: 15, nodes: []} == + assert {:integer, [], 15} == Ovo.Registry.run_chain([hash2, hash1], [7]) - assert %Ast{kind: :integer, value: 14, nodes: []} == + assert {:integer, [], 14} == Ovo.Runner.shake(hash2) - assert %Ast{kind: :integer, value: 10, nodes: []} == + assert {:integer, [], 10} == Ovo.Runner.shake(hash2) - assert %Ast{kind: :integer, value: 16, nodes: []} == + assert {:integer, [], 16} == Ovo.Runner.run("1I9Jp", [8]) end @@ -55,10 +54,10 @@ defmodule OvoDistributionTest do Ovo.Registry.run_chain([ovo_adder, ovo_add_one], [a, b]) end - assert add_and_add_one.(2, 3) == %Ovo.Ast{kind: :integer, nodes: [], value: 6} + assert add_and_add_one.(2, 3) == {:integer, [], 6} add_and_add_one.(6, 6) Ovo.Runner.shake(ovo_add_one) - assert Ovo.Runner.shake(ovo_add_one) == %Ovo.Ast{kind: :integer, nodes: [], value: 6} + assert Ovo.Runner.shake(ovo_add_one) == {:integer, [], 6} end test "program linking 3" do @@ -106,9 +105,9 @@ defmodule OvoDistributionTest do "baz" ) - %Ovo.Ast{value: 5} = Ovo.Runner.run(ovo_adder, [2, 3]) - %Ovo.Ast{value: 10} = Ovo.Runner.run(ovo_times2, [5]) - %Ovo.Ast{value: 10} = Ovo.Registry.run_chain([ovo_adder, ovo_times2], [2, 3]) + {:integer, [], 5} = Ovo.Runner.run(ovo_adder, [2, 3]) + {:integer, [], 10} = Ovo.Runner.run(ovo_times2, [5]) + {:integer, [], 10} = Ovo.Registry.run_chain([ovo_adder, ovo_times2], [2, 3]) {:ok, dependent_program} = Ovo.Runner.register( @@ -118,7 +117,7 @@ defmodule OvoDistributionTest do "test" ) - %Ovo.Ast{value: 4} = Ovo.Runner.run(dependent_program, []) - %Ovo.Ast{value: 4} = Ovo.Runner.shake(dependent_program) + {:integer, [], 4} = Ovo.Runner.run(dependent_program, []) + {:integer, [], 4} = Ovo.Runner.shake(dependent_program) end end diff --git a/ovo/test/ovo_shake_test.exs b/ovo/test/ovo_shake_test.exs index 30e9ae2..c2f0af9 100644 --- a/ovo/test/ovo_shake_test.exs +++ b/ovo/test/ovo_shake_test.exs @@ -14,7 +14,7 @@ defmodule OvoTestRecursiveShakenSpecialCase do fibs(5) """ - assert {%Ovo.Ast{kind: :integer, nodes: [], value: 8}, _} = Ovo.run(code) + assert {{:integer, [], 8}, _} = Ovo.run(code) end test "another shake test" do @@ -28,7 +28,7 @@ defmodule OvoTestRecursiveShakenSpecialCase do add(a, shake(add_one)) """ - assert {%Ovo.Ast{kind: :integer, nodes: [], value: 7}, _} = Ovo.run(code) + assert {{:integer, [], 7}, _} = Ovo.run(code) end test "access test" do @@ -38,6 +38,6 @@ defmodule OvoTestRecursiveShakenSpecialCase do add_one(fonk) """ - assert {%Ovo.Ast{kind: :integer, nodes: [], value: 2}, _} = Ovo.run(code, %{"arg0" => 1}) + assert {{:integer, [], 2}, _} = Ovo.run(code, %{"arg0" => 1}) end end diff --git a/ovo/test/ovo_test.exs b/ovo/test/ovo_test.exs index 508207a..462d3fb 100644 --- a/ovo/test/ovo_test.exs +++ b/ovo/test/ovo_test.exs @@ -146,45 +146,45 @@ defmodule OvoTest do test "Parses an argless function call" do {:ok, - %Ovo.Ast{ - kind: :root, - nodes: [ - %Ovo.Ast{kind: :call, nodes: [], value: %Ovo.Ast{kind: :symbol, nodes: [], value: "foo"}} + { + :root, + [ + {:call, [], {:symbol, [], "foo"}} ], - value: nil + nil }, []} = parse("foo()") end test "Parses a single-arg function call" do {:ok, - %Ovo.Ast{ - kind: :root, - nodes: [ - %Ovo.Ast{ - kind: :call, - nodes: [%Ovo.Ast{kind: :symbol, nodes: [], value: "bar"}], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "foo"} + { + :root, + [ + { + :call, + [{:symbol, [], "bar"}], + {:symbol, [], "foo"} } ], - value: nil + nil }, []} = parse("foo(bar)") end test "Parses a multi-arg function call" do {:ok, - %Ovo.Ast{ - kind: :root, - nodes: [ - %Ovo.Ast{ - kind: :call, - nodes: [ - %Ovo.Ast{kind: :symbol, nodes: [], value: "bar"}, - %Ovo.Ast{kind: :symbol, nodes: [], value: "baz"} + { + :root, + [ + { + :call, + [ + {:symbol, [], "bar"}, + {:symbol, [], "baz"} ], - value: %Ovo.Ast{kind: :symbol, nodes: [], value: "foo"} + {:symbol, [], "foo"} } ], - value: nil + nil }, []} = parse("foo(bar, baz)") end @@ -310,7 +310,7 @@ defmodule OvoTest do test "basic evaluation" do program = "addone = \\a -> add(1, a) end addone(2)" - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program) + {{:integer, [], 3}, _} = Ovo.run(program) end test "basic evaluation 2" do @@ -327,8 +327,8 @@ defmodule OvoTest do """ end - {%Ovo.Ast{kind: :integer, nodes: [], value: 2}, _} = Ovo.run(program.(2)) - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program.(0)) + {{:integer, [], 2}, _} = Ovo.run(program.(2)) + {{:integer, [], 3}, _} = Ovo.run(program.(0)) end test "basic shakeing evaluation 2" do @@ -349,8 +349,8 @@ defmodule OvoTest do """ end - {%Ovo.Ast{kind: :integer, nodes: [], value: 2}, _} = Ovo.run(program.(2)) - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program.(0)) + {{:integer, [], 2}, _} = Ovo.run(program.(2)) + {{:integer, [], 3}, _} = Ovo.run(program.(0)) end test "basic recursion" do @@ -365,7 +365,7 @@ defmodule OvoTest do radd(0) """ - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program) + {{:integer, [], 3}, _} = Ovo.run(program) end test "basic recursion 2" do @@ -382,7 +382,7 @@ defmodule OvoTest do radd(0) """ - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program, %{}) + {{:integer, [], 3}, _} = Ovo.run(program, %{}) end test "basic recursion and nesting 3" do @@ -407,7 +407,7 @@ defmodule OvoTest do radd(0) """ - {%Ovo.Ast{kind: :integer, nodes: [], value: 3}, _} = Ovo.run(program) + {{:integer, [], 3}, _} = Ovo.run(program) end test "mutation tests" do @@ -423,7 +423,7 @@ defmodule OvoTest do bar(5) """ - {%Ovo.Ast{kind: :integer, nodes: [], value: 9}, _} = Ovo.run(program) + {{:integer, [], 9}, _} = Ovo.run(program) end test "rewrites a complex example" do @@ -461,7 +461,7 @@ defmodule OvoTest do hex(hash) """ - assert {%Ovo.Ast{kind: :string, nodes: [], value: "519E91F5"}, _} = + assert {{:string, [], "519E91F5"}, _} = Ovo.run(input, "The quick brown fox jumps over the lazy dog") end end