Skip to content

Commit 0f1584c

Browse files
author
José Valim
committed
Initial work on RecordRewriter
1 parent 49fa59d commit 0f1584c

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# This is an optimization Elixir runs on function clauses.
2+
# Whenever a variables matches against a record (a tagged
3+
# tuple), this information is stored in order to optimize
4+
# record calls.
5+
defmodule Kernel.RecordRewriter do
6+
@moduledoc false
7+
8+
def optimize_clause(clause) do
9+
optimize_clause(clause, :orddict.new)
10+
end
11+
12+
## Clause
13+
14+
defp optimize_clause({ :clause, line, args, guards, body }, dict) do
15+
{ args, dict } = optimize_args(args, dict)
16+
{ body, dict, res } = optimize_body(body, dict, [])
17+
{ { :clause, line, args, guards, body }, dict, res }
18+
end
19+
20+
defp optimize_args(args, dict) do
21+
Enum.map_reduce args, dict, fn(arg, acc) ->
22+
{ new_arg, new_acc, _res } = optimize_expr(arg, acc)
23+
{ new_arg, new_acc }
24+
end
25+
end
26+
27+
defp optimize_body([], dict, _acc) do
28+
{ [], dict, nil }
29+
end
30+
31+
defp optimize_body([h], dict, acc) do
32+
{ new_expr, new_dict, new_res } = optimize_expr(h, dict)
33+
{ Enum.reverse([new_expr|acc]), new_dict, new_res }
34+
end
35+
36+
defp optimize_body([h|t], dict, acc) do
37+
{ new_expr, new_dict, _ } = optimize_expr(h, dict)
38+
optimize_body(t, new_dict, [new_expr|acc])
39+
end
40+
41+
## Expr
42+
43+
defp optimize_expr({ :match, line, left, right }, dict) do
44+
{ left, dict, left_res } = optimize_expr(left, dict)
45+
{ right, dict, right_res } = optimize_expr(right, dict)
46+
47+
match = { :match, line, left, right }
48+
49+
if right_res do
50+
dict = assign_vars(extract_vars(left, []), dict, right_res)
51+
end
52+
53+
if left_res do
54+
dict = assign_vars(extract_vars(right, []), dict, left_res)
55+
end
56+
57+
{ match, dict, right_res || left_res }
58+
end
59+
60+
defp optimize_expr({ :tuple, line, args }, dict) do
61+
{ args, dict, args_res } = optimize_tuple_args(args, dict)
62+
63+
res =
64+
case args do
65+
[{ :atom, _, atom }|t] -> if is_record?(atom), do: atom
66+
_ -> nil
67+
end
68+
69+
{ { :tuple, line, args }, dict, { res, args_res } }
70+
end
71+
72+
defp optimize_expr({ :var, _, name } = var, dict) do
73+
case :orddict.find(name, dict) do
74+
{ :ok, res } -> { var, dict, { res, nil } }
75+
:error -> { var, dict, nil }
76+
end
77+
end
78+
79+
defp optimize_expr(other, dict) do
80+
{ other, dict, nil }
81+
end
82+
83+
## Match related
84+
85+
defp optimize_tuple_args(args, dict) do
86+
{ final_args, { final_dict, final_acc } } =
87+
Enum.map_reduce args, { dict, [] }, fn(arg, { acc_dict, acc_res }) ->
88+
{ new_arg, new_acc, res } = optimize_expr(arg, acc_dict)
89+
{ new_arg, { new_acc, [res|acc_res] } }
90+
end
91+
92+
{ final_args, final_dict, Enum.reverse(final_acc) }
93+
end
94+
95+
defp assign_vars([key|t], dict, { _, value } = res) when is_list(key) and is_list(value) and length(key) == length(value) do
96+
assign_vars t, assign_nested_vars(key, dict, value), res
97+
end
98+
99+
defp assign_vars([key|t], dict, { value, _ } = res) when is_atom(key) and value != nil do
100+
assign_vars t, :orddict.store(key, value, dict), res
101+
end
102+
103+
defp assign_vars([_|t], dict, res) do
104+
assign_vars t, dict, res
105+
end
106+
107+
defp assign_vars([], dict, _res) do
108+
dict
109+
end
110+
111+
defp assign_nested_vars([vars|vt], dict, [res|rt]) do
112+
assign_nested_vars(vt, assign_vars(vars, dict, res), rt)
113+
end
114+
115+
defp assign_nested_vars([], dict, []) do
116+
dict
117+
end
118+
119+
defp extract_vars({ :match, _, left, right }, vars) do
120+
vars = extract_vars(right, vars)
121+
extract_vars(left, vars)
122+
end
123+
124+
defp extract_vars({ :var, _, name }, vars) do
125+
[name|vars]
126+
end
127+
128+
defp extract_vars({ :tuple, _, args }, vars) do
129+
[Enum.map(args, extract_vars(&1, []))|vars]
130+
end
131+
132+
defp extract_vars(_, vars) do
133+
vars
134+
end
135+
136+
## Record helpers
137+
138+
# TODO: Implement proper record check
139+
defp is_record?(_h) do
140+
true
141+
end
142+
end
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
Code.require_file "../../test_helper.exs", __FILE__
2+
3+
defmodule Kernel.RecordRewriterTest do
4+
use ExUnit.Case, async: true
5+
6+
import Kernel.RecordRewriter
7+
8+
## Helpers
9+
10+
defmacrop clause(expr) do
11+
quote do: extract_clause(unquote(Macro.escape(expr)))
12+
end
13+
14+
defp extract_clause(fun) do
15+
{ { :fun, _, { :clauses, clauses } }, _ } =
16+
:elixir_translator.translate_each(fun, :elixir.scope_for_eval([]))
17+
hd(clauses)
18+
end
19+
20+
## Dictionary tests
21+
22+
test "simple atom" do
23+
clause = clause(fn -> :foo end)
24+
assert optimize_clause(clause) == { clause, [], nil }
25+
end
26+
27+
test "with left-side arg match" do
28+
clause = clause(fn(arg = Macro.Env[]) -> :foo end)
29+
assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
30+
end
31+
32+
test "with right-side arg match" do
33+
clause = clause(fn(Macro.Env[] = arg) -> :foo end)
34+
assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
35+
end
36+
37+
test "with nested left-side arg match" do
38+
clause = clause(fn(arg = other = Macro.Env[]) -> :foo end)
39+
assert optimize_clause(clause) == { clause, [arg: Macro.Env, other: Macro.Env], nil }
40+
end
41+
42+
test "with nested right-side arg match" do
43+
clause = clause(fn(Macro.Env[] = arg = other) -> :foo end)
44+
assert optimize_clause(clause) == { clause, [arg: Macro.Env, other: Macro.Env], nil }
45+
end
46+
47+
test "with deep left-side arg match" do
48+
clause = clause(fn({ x, arg = Macro.Env[] }) -> :foo end)
49+
assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
50+
end
51+
52+
test "with deep right-side arg match" do
53+
clause = clause(fn({ x, Macro.Env[] = arg }) -> :foo end)
54+
assert optimize_clause(clause) == { clause, [arg: Macro.Env], nil }
55+
end
56+
57+
test "with tuple match" do
58+
clause = clause(fn({ x, y } = { Macro.Env[], Range[] }) -> :foo end)
59+
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
60+
61+
clause = clause(fn({ Macro.Env[], y } = { x, Range[] }) -> :foo end)
62+
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range], nil }
63+
end
64+
65+
test "with body variable" do
66+
clause = clause(fn(x = Macro.Env[]) -> y = x end)
67+
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Macro.Env], { Macro.Env, nil } }
68+
end
69+
70+
test "with body variable overridden" do
71+
clause = clause(fn(x = Macro.Env[], y = Range[]) -> y = x end)
72+
assert optimize_clause(clause) == { clause, [x: Macro.Env, y: Range, "y@1": Macro.Env], { Macro.Env, nil } }
73+
end
74+
end

0 commit comments

Comments
 (0)