263 lines
9.5 KiB
Erlang
263 lines
9.5 KiB
Erlang
%% Handle code related to args, guard and -> matching for case,
|
|
%% fn, receive and friends. try is handled in elixir_try.
|
|
-module(elixir_clauses).
|
|
-export([
|
|
assigns/3, assigns_block/5, assigns_block/6, extract_splat_guards/1,
|
|
get_pairs/4, get_pairs/5, match/3, extract_args/1, extract_guards/1]).
|
|
-include("elixir.hrl").
|
|
|
|
%% Get pairs from a clause.
|
|
|
|
get_pairs(Meta, Key, Clauses, S) ->
|
|
get_pairs(Meta, Key, Clauses, S, false).
|
|
|
|
get_pairs(Meta, Key, Clauses, S, AllowNil) ->
|
|
case lists:keyfind(Key, 1, Clauses) of
|
|
{ Key, { '->', _, Pairs } } ->
|
|
[{ Key, PMeta, Left, Right } || { Left, PMeta, Right } <- Pairs];
|
|
{ Key, nil } when AllowNil ->
|
|
[];
|
|
{ Key, _ } ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "expected pairs with -> for key ~ts", [Key]);
|
|
_ ->
|
|
[]
|
|
end.
|
|
|
|
% Function for translating assigns.
|
|
|
|
assigns(Fun, Args, #elixir_scope{context=Context, temp_vars=TempVars,
|
|
backup_vars=BackupVars, vars=Vars} = S) when Context /= match ->
|
|
{ Result, NewS } = assigns(Fun, Args, S#elixir_scope{context=match,
|
|
temp_vars=ordsets:new(), backup_vars=Vars}),
|
|
{ Result, NewS#elixir_scope{context=Context,
|
|
temp_vars=TempVars, backup_vars=BackupVars} };
|
|
|
|
assigns(Fun, Args, S) -> Fun(Args, S).
|
|
|
|
%% Function for translating a block that is preceeded by an
|
|
%% assignment and optional guards. This is used by def* and fn.
|
|
|
|
assigns_block(Line, Fun, BareArgs, Exprs, S) ->
|
|
{ Args, Guards } = extract_guards(BareArgs),
|
|
assigns_block(Line, Fun, Args, Exprs, Guards, S).
|
|
|
|
assigns_block(Line, Fun, Args, Exprs, Guards, S) when is_integer(Line) ->
|
|
{ TArgs, SA } = assigns(Fun, Args, S#elixir_scope{extra_guards=[]}),
|
|
{ TExprs, SE } = elixir_translator:translate(Exprs, SA#elixir_scope{extra_guards=nil}),
|
|
|
|
FArgs = listify(TArgs),
|
|
SG = SA#elixir_scope{context=guard, extra_guards=nil},
|
|
Extra = SA#elixir_scope.extra_guards,
|
|
|
|
FGuards = case Guards of
|
|
[] -> case Extra of [] -> []; _ -> [Extra] end;
|
|
_ -> [translate_guard(Line, Guard, Extra, SG) || Guard <- Guards]
|
|
end,
|
|
|
|
% Uncompact expressions from the block.
|
|
case TExprs of
|
|
[{ block, _, FExprs }] -> [];
|
|
_ -> FExprs = TExprs
|
|
end,
|
|
|
|
{ { clause, Line, FArgs, FGuards, FExprs }, SE }.
|
|
|
|
% Translate/Extract guards from the given expression.
|
|
|
|
translate_guard(Line, Guard, Extra, S) ->
|
|
[element(1, elixir_translator:translate_each(elixir_quote:linify(Line, Guard), S))|Extra].
|
|
|
|
extract_guards({ 'when', _, [Left, Right] }) -> { Left, extract_or_clauses(Right, []) };
|
|
extract_guards(Else) -> { Else, [] }.
|
|
|
|
extract_or_clauses({ 'when', _, [Left, Right] }, Acc) -> extract_or_clauses(Right, [Left|Acc]);
|
|
extract_or_clauses(Term, Acc) -> [Term|Acc].
|
|
|
|
% Extract name and args from the given expression.
|
|
|
|
extract_args({ { '.', _, [Name] }, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
|
extract_args({ Name, _, Args }) when is_atom(Name), is_atom(Args) -> { Name, [] };
|
|
extract_args({ Name, _, Args }) when is_atom(Name), is_list(Args) -> { Name, Args };
|
|
extract_args(_) -> error.
|
|
|
|
% Extract guards when multiple left side args are allowed.
|
|
|
|
extract_splat_guards([{ 'when', _, [_,_|_] = Args }]) ->
|
|
{ Left, Right } = elixir_utils:split_last(Args),
|
|
{ Left, extract_or_clauses(Right, []) };
|
|
extract_splat_guards(Else) ->
|
|
{ Else, [] }.
|
|
|
|
% Function for translating macros with match style like case and receive.
|
|
|
|
match(Meta, Clauses, #elixir_scope{clause_vars=C1} = S) ->
|
|
{ TC, TS } = do_match(Meta, Clauses, S#elixir_scope{clause_vars=orddict:new()}),
|
|
C2 = TS#elixir_scope.clause_vars,
|
|
{ TC, TS#elixir_scope{clause_vars=elixir_scope:merge_clause_vars(C1, C2)} }.
|
|
|
|
do_match(_Meta, [], S) ->
|
|
{ [], S };
|
|
|
|
do_match(_Meta, [DecoupledClause], S) ->
|
|
{ TDecoupledClause, TS } = each_clause(DecoupledClause, S),
|
|
{ [TDecoupledClause], TS };
|
|
|
|
do_match(Meta, DecoupledClauses, S) ->
|
|
% Transform tree just passing the variables counter forward
|
|
% and storing variables defined inside each clause.
|
|
Transformer = fun(X, {Acc, CV}) ->
|
|
{ TX, TAcc } = each_clause(X, Acc),
|
|
{ TX, { merge_clauses_scope(S, TAcc), [TAcc#elixir_scope.clause_vars|CV] } }
|
|
end,
|
|
|
|
{ TClauses, { TS, ReverseCV } } = lists:mapfoldl(Transformer, {S, []}, DecoupledClauses),
|
|
|
|
% Now get all the variables defined inside each clause
|
|
CV = lists:reverse(ReverseCV),
|
|
AllVars = lists:foldl(fun(KV, Acc) ->
|
|
elixir_scope:merge_clause_vars(Acc, KV)
|
|
end, orddict:new(), CV),
|
|
|
|
% Create a new scope that contains a list of all variables
|
|
% defined inside all the clauses. It returns this new scope and
|
|
% a list of tuples where the first element is the variable name,
|
|
% the second one is the new pointer to the variable and the third
|
|
% is the old pointer.
|
|
{ FinalVars, FS } = lists:mapfoldl(fun({ Key, Ref }, Acc) ->
|
|
normalize_vars(Key, Ref, Acc)
|
|
end, TS, AllVars),
|
|
|
|
% Expand all clauses by adding a match operation at the end
|
|
% that assigns variables missing in one clause to the others.
|
|
expand_clauses(?line(Meta), TClauses, CV, FinalVars, [], FS).
|
|
|
|
expand_clauses(Line, [Clause|T], [ClauseVars|V], FinalVars, Acc, S) ->
|
|
case generate_match_vars(FinalVars, ClauseVars, [], []) of
|
|
{ [], [] } ->
|
|
expand_clauses(Line, T, V, FinalVars, [Clause|Acc], S);
|
|
{ Left, Right } ->
|
|
MatchExpr = generate_match(Line, Left, Right),
|
|
ClauseExprs = element(5, Clause),
|
|
[Final|RawClauseExprs] = lists:reverse(ClauseExprs),
|
|
|
|
% If the last sentence has a match clause, we need to assign its value
|
|
% in the variable list. If not, we insert the variable list before the
|
|
% final clause in order to keep it tail call optimized.
|
|
{ FinalClauseExprs, FS } = case has_match_tuple(Final) of
|
|
true ->
|
|
case Final of
|
|
{ match, _, { var, _, UserVarName } = UserVar, _ } when UserVarName /= '_' ->
|
|
{ [UserVar,MatchExpr,Final|RawClauseExprs], S };
|
|
_ ->
|
|
{ StorageVar, SS } = elixir_scope:build_erl_var(Line, S),
|
|
StorageExpr = { match, Line, StorageVar, Final },
|
|
{ [StorageVar,MatchExpr,StorageExpr|RawClauseExprs], SS }
|
|
end;
|
|
false ->
|
|
{ [Final,MatchExpr|RawClauseExprs], S }
|
|
end,
|
|
|
|
FinalClause = setelement(5, Clause, lists:reverse(FinalClauseExprs)),
|
|
expand_clauses(Line, T, V, FinalVars, [FinalClause|Acc], FS)
|
|
end;
|
|
|
|
expand_clauses(_Line, [], [], _FinalVars, Acc, S) ->
|
|
{ lists:reverse(Acc), S }.
|
|
|
|
% Handle each key/value clause pair and translate them accordingly.
|
|
|
|
each_clause({ do, Meta, [Condition], Expr }, S) ->
|
|
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
|
|
|
each_clause({ else, Meta, [Condition], Expr }, S) ->
|
|
assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], S);
|
|
|
|
each_clause({ 'after', Meta, [Condition], Expr }, S) ->
|
|
{ TCondition, SC } = elixir_translator:translate_each(Condition, S),
|
|
{ TBody, SB } = elixir_translator:translate([Expr], SC),
|
|
{ { clause, ?line(Meta), [TCondition], [], TBody }, SB };
|
|
|
|
each_clause({ Key, Meta, [_|_], _ }, S) when Key == do; Key == 'after' ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for ~ts", [Key]);
|
|
|
|
each_clause({ Key, Meta, _, _ }, S) ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts", [Key]).
|
|
|
|
% Check if the given expression is a match tuple.
|
|
% This is a small optimization to allow us to change
|
|
% existing assignments instead of creating new ones every time.
|
|
|
|
has_match_tuple({'receive', _, _, _, _}) ->
|
|
true;
|
|
|
|
has_match_tuple({'receive', _, _}) ->
|
|
true;
|
|
|
|
has_match_tuple({'case', _, _, _}) ->
|
|
true;
|
|
|
|
has_match_tuple({match, _, _, _}) ->
|
|
true;
|
|
|
|
has_match_tuple({'fun', _, { clauses, _ }}) ->
|
|
false;
|
|
|
|
has_match_tuple(H) when is_tuple(H) ->
|
|
has_match_tuple(tuple_to_list(H));
|
|
|
|
has_match_tuple(H) when is_list(H) ->
|
|
lists:any(fun has_match_tuple/1, H);
|
|
|
|
has_match_tuple(_) -> false.
|
|
|
|
% Normalize the given var in between clauses
|
|
% by picking one value as reference and retriving
|
|
% its previous value.
|
|
|
|
normalize_vars(Key, Value, #elixir_scope{vars=Vars,clause_vars=ClauseVars} = S) ->
|
|
FS = S#elixir_scope{
|
|
vars=orddict:store(Key, Value, Vars),
|
|
clause_vars=orddict:store(Key, Value, ClauseVars)
|
|
},
|
|
|
|
Expr = case orddict:find(Key, Vars) of
|
|
{ ok, OldValue } -> { var, 0, OldValue };
|
|
error -> { atom, 0, nil }
|
|
end,
|
|
|
|
{ { Key, Value, Expr }, FS }.
|
|
|
|
% Generate match vars by checking if they were updated
|
|
% or not and assigning the previous value.
|
|
|
|
generate_match_vars([{ Key, NewValue, OldValue }|T], ClauseVars, Left, Right) ->
|
|
case orddict:find(Key, ClauseVars) of
|
|
{ ok, NewValue } ->
|
|
generate_match_vars(T, ClauseVars, Left, Right);
|
|
{ ok, ClauseValue } ->
|
|
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [{ var, 0, ClauseValue }|Right]);
|
|
error ->
|
|
generate_match_vars(T, ClauseVars, [{ var, 0, NewValue }|Left], [OldValue|Right])
|
|
end;
|
|
|
|
generate_match_vars([], _ClauseVars, Left, Right) ->
|
|
{ Left, Right }.
|
|
|
|
generate_match(Line, [Left], [Right]) ->
|
|
{ match, Line, Left, Right };
|
|
|
|
generate_match(Line, LeftVars, RightVars) ->
|
|
{ match, Line, { tuple, Line, LeftVars }, { tuple, Line, RightVars } }.
|
|
|
|
listify(Expr) when not is_list(Expr) -> [Expr];
|
|
listify(Expr) -> Expr.
|
|
|
|
%% We don't use umergec because imports, aliases and
|
|
%% what not are not passed from one clause to the other.
|
|
merge_clauses_scope(S1, S2) ->
|
|
S1#elixir_scope{
|
|
counter=S2#elixir_scope.counter,
|
|
extra_guards=S2#elixir_scope.extra_guards,
|
|
super=S1#elixir_scope.super orelse S2#elixir_scope.super,
|
|
caller=S1#elixir_scope.caller orelse S2#elixir_scope.caller
|
|
}. |