-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
elixir_erl_var.erl
123 lines (99 loc) · 3.74 KB
/
elixir_erl_var.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
%% Convenience functions used to manipulate scope and its variables.
-module(elixir_erl_var).
-export([
translate/4, assign/2, build/2,
load_binding/2, dump_binding/4,
from_env/1, from_env/2
]).
-include("elixir.hrl").
%% VAR HANDLING
translate(Meta, '_', _Kind, S) ->
{{var, ?ann(Meta), '_'}, S};
translate(Meta, Name, Kind, #elixir_erl{var_names=VarNames} = S) ->
{version, Version} = lists:keyfind(version, 1, Meta),
case VarNames of
#{Version := ErlName} -> {{var, ?ann(Meta), ErlName}, S};
#{} when Kind /= nil -> assign(Meta, '_', Version, S);
#{} -> assign(Meta, Name, Version, S)
end.
assign(Meta, #elixir_erl{var_names=VarNames} = S) ->
Version = -(map_size(VarNames)+1),
ExVar = {var, [{version, Version} | Meta], ?var_context},
{ErlVar, SV} = assign(Meta, '_', Version, S),
{ExVar, ErlVar, SV}.
assign(Meta, Name, Version, #elixir_erl{var_names=VarNames} = S) ->
{NewVar, NS} = build(Name, S),
NewVarNames = VarNames#{Version => NewVar},
{{var, ?ann(Meta), NewVar}, NS#elixir_erl{var_names=NewVarNames}}.
build(Key, #elixir_erl{counter=Counter} = S) ->
Count =
case Counter of
#{Key := Val} -> Val + 1;
_ -> 1
end,
{build_name(Key, Count),
S#elixir_erl{counter=Counter#{Key => Count}}}.
build_name('_', Count) -> list_to_atom("_@" ++ integer_to_list(Count));
build_name(Name, Count) -> list_to_atom("_" ++ atom_to_list(Name) ++ "@" ++ integer_to_list(Count)).
%% BINDINGS
from_env(#{versioned_vars := Read} = Env) ->
VarsList = to_erl_vars(maps:values(Read), 0),
{VarsList, from_env(Env, maps:from_list(VarsList))}.
from_env(#{context := Context}, VarsMap) ->
#elixir_erl{
context=Context,
var_names=VarsMap,
counter=#{'_' => map_size(VarsMap)}
}.
to_erl_vars([Version | Versions], Counter) ->
[{Version, to_erl_var(Counter)} | to_erl_vars(Versions, Counter + 1)];
to_erl_vars([], _Counter) ->
[].
to_erl_var(Counter) ->
list_to_atom("_@" ++ integer_to_list(Counter)).
load_binding(Binding, Prune) ->
load_binding(Binding, #{}, [], [], 0, Prune).
load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter, Prune) ->
{Pair, Value} = load_pair(Binding),
case ExVars of
#{Pair := VarCounter} ->
ErlVar = to_erl_var(VarCounter),
load_binding(NextBindings, ExVars, ErlVars, [{ErlVar, Value} | Normalized], Counter, Prune);
#{} ->
ErlVar = to_erl_var(Counter),
load_binding(
NextBindings,
ExVars#{Pair => Counter},
[{Counter, ErlVar} | ErlVars],
[{ErlVar, Value} | Normalized],
Counter + 1,
Prune
)
end;
load_binding([], ExVars, ErlVars, Normalized, Counter, true) ->
load_binding([{{elixir, prune_binding}, true}], ExVars, ErlVars, Normalized, Counter, false);
load_binding([], ExVars, ErlVars, Normalized, _Counter, false) ->
{ExVars, maps:from_list(ErlVars), maps:from_list(lists:reverse(Normalized))}.
load_pair({Key, Value}) when is_atom(Key) -> {{Key, nil}, Value};
load_pair({Pair, Value}) -> {Pair, Value}.
dump_binding(Binding, ErlS, ExS, PruneBefore) ->
#elixir_erl{var_names=ErlVars} = ErlS,
#elixir_ex{vars={ExVars, _}, unused={Unused, _}} = ExS,
maps:fold(fun
({Var, Kind} = Pair, Version, {B, V})
when is_atom(Kind),
%% If the variable is part of the pruning (usually the input binding)
%% and is unused, we removed it from vars.
Version > PruneBefore orelse is_map_key({Pair, Version}, Unused) ->
Key = case Kind of
nil -> Var;
_ -> Pair
end,
ErlName = maps:get(Version, ErlVars),
Value = maps:get(ErlName, Binding, nil),
{[{Key, Value} | B], V};
(Pair, _, {B, V}) when PruneBefore >= 0 ->
{B, maps:remove(Pair, V)};
(_, _, Acc) ->
Acc
end, {[], ExVars}, ExVars).