Skip to content
Browse files

Initial work on RecordRewriter

  • Loading branch information...
1 parent 49fa59d commit 0f1584c0faa3c3e20f911e1d10b16c50351fa403 @josevalim josevalim committed Dec 2, 2012
Showing with 216 additions and 0 deletions.
  1. +142 −0 lib/elixir/lib/kernel/record_rewriter.ex
  2. +74 −0 lib/elixir/test/elixir/kernel/record_rewriter_test.exs
View
142 lib/elixir/lib/kernel/record_rewriter.ex
@@ -0,0 +1,142 @@
+# This is an optimization Elixir runs on function clauses.
+# Whenever a variables matches against a record (a tagged
+# tuple), this information is stored in order to optimize
+# record calls.
+defmodule Kernel.RecordRewriter do
+ @moduledoc false
+
+ def optimize_clause(clause) do
+ optimize_clause(clause, :orddict.new)
+ end
+
+ ## Clause
+
+ defp optimize_clause({ :clause, line, args, guards, body }, dict) do
+ { args, dict } = optimize_args(args, dict)
+ { body, dict, res } = optimize_body(body, dict, [])
+ { { :clause, line, args, guards, body }, dict, res }
+ end
+
+ defp optimize_args(args, dict) do
+ Enum.map_reduce args, dict, fn(arg, acc) ->
+ { new_arg, new_acc, _res } = optimize_expr(arg, acc)
+ { new_arg, new_acc }
+ end
+ end
+
+ defp optimize_body([], dict, _acc) do
+ { [], dict, nil }
+ end
+
+ defp optimize_body([h], dict, acc) do
+ { new_expr, new_dict, new_res } = optimize_expr(h, dict)
+ { Enum.reverse([new_expr|acc]), new_dict, new_res }
+ end
+
+ defp optimize_body([h|t], dict, acc) do
+ { new_expr, new_dict, _ } = optimize_expr(h, dict)
+ optimize_body(t, new_dict, [new_expr|acc])
+ end
+
+ ## Expr
+
+ defp optimize_expr({ :match, line, left, right }, dict) do
+ { left, dict, left_res } = optimize_expr(left, dict)
+ { right, dict, right_res } = optimize_expr(right, dict)
+
+ match = { :match, line, left, right }
+
+ if right_res do
+ dict = assign_vars(extract_vars(left, []), dict, right_res)
+ end
+
+ if left_res do
+ dict = assign_vars(extract_vars(right, []), dict, left_res)
+ end
+
+ { match, dict, right_res || left_res }
+ end
+
+ defp optimize_expr({ :tuple, line, args }, dict) do
+ { args, dict, args_res } = optimize_tuple_args(args, dict)
+
+ res =
+ case args do
+ [{ :atom, _, atom }|t] -> if is_record?(atom), do: atom
+ _ -> nil
+ end
+
+ { { :tuple, line, args }, dict, { res, args_res } }
+ end
+
+ defp optimize_expr({ :var, _, name } = var, dict) do
+ case :orddict.find(name, dict) do
+ { :ok, res } -> { var, dict, { res, nil } }
+ :error -> { var, dict, nil }
+ end
+ end
+
+ defp optimize_expr(other, dict) do
+ { other, dict, nil }
+ end
+
+ ## Match related
+
+ defp optimize_tuple_args(args, dict) do
+ { final_args, { final_dict, final_acc } } =
+ Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
+ { new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
+ { new_arg, { new_acc, [res|acc_res] } }
+ end
+
+ { final_args, final_dict, Enum.reverse(final_acc) }
+ end
+
+ defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
+ assign_vars t, assign_nested_vars(key, dict, value), res
+ end
+
+ defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
+ assign_vars t, :orddict.store(key, value, dict), res
+ end
+
+ defp assign_vars([_|t], dict, res) do
+ assign_vars t, dict, res
+ end
+
+ defp assign_vars([], dict, _res) do
+ dict
+ end
+
+ defp assign_nested_vars([vars|vt], dict, [res|rt]) do
+ assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
+ end
+
+ defp assign_nested_vars([], dict, []) do
+ dict
+ end
+
+ defp extract_vars({ :match, _, left, right }, vars) do
+ vars = extract_vars(right, vars)
+ extract_vars(left, vars)
+ end
+
+ defp extract_vars({ :var, _, name }, vars) do
+ [name|vars]
+ end
+
+ defp extract_vars({ :tuple, _, args }, vars) do
+ [Enum.map(args, extract_vars(&1, []))|vars]
+ end
+
+ defp extract_vars(_, vars) do
+ vars
+ end
+
+ ## Record helpers
+
+ # TODO: Implement proper record check
+ defp is_record?(_h) do
+ true
+ end
+end
View
74 lib/elixir/test/elixir/kernel/record_rewriter_test.exs
@@ -0,0 +1,74 @@
+Code.require_file "../../test_helper.exs", __FILE__
+
+defmodule Kernel.RecordRewriterTest do
+ use ExUnit.Case, async: true
+
+ import Kernel.RecordRewriter
+
+ ## Helpers
+
+ defmacrop clause(expr) do
+ quote do: extract_clause(unquote(Macro.escape(expr)))
+ end
+
+ defp extract_clause(fun) do
+ { { :fun, _, { :clauses, clauses } }, _ } =
+ :elixir_translator.translate_each(fun, :elixir.scope_for_eval([]))
+ hd(clauses)
+ end
+
+ ## Dictionary tests
+
+ test "simple atom" do
+ clause = clause(fn -> :foo end)
+ assert optimize_clause(clause) == { clause, [], nil }
+ end
+
+ test "with left-side arg match" do
+ clause = clause(fn(arg = Macro.Env[]) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
+ end
+
+ test "with right-side arg match" do
+ clause = clause(fn(Macro.Env[] = arg) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
+ end
+
+ test "with nested left-side arg match" do
+ clause = clause(fn(arg = other = Macro.Env[]) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env, other: Macro.Env], nil }
+ end
+
+ test "with nested right-side arg match" do
+ clause = clause(fn(Macro.Env[] = arg = other) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env, other: Macro.Env], nil }
+ end
+
+ test "with deep left-side arg match" do
+ clause = clause(fn({ x, arg = Macro.Env[] }) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
+ end
+
+ test "with deep right-side arg match" do
+ clause = clause(fn({ x, Macro.Env[] = arg }) -> :foo end)
+ assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
+ end
+
+ test "with tuple match" do
+ clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
+ assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
+
+ clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
+ assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
+ end
+
+ test "with body variable" do
+ clause = clause(fn(x = Macro.Env[]) -> y = x end)
+ assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], { Macro.Env, nil } }
+ end
+
+ test "with body variable overridden" do
+ clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
+ assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
+ end
+end

0 comments on commit 0f1584c

Please sign in to comment.
Something went wrong with that request. Please try again.