272 lines
9.7 KiB
Erlang
272 lines
9.7 KiB
Erlang
-module(elixir_try).
|
|
-export([clauses/3, format_error/1]).
|
|
-import(elixir_scope, [umergec/2]).
|
|
-include("elixir.hrl").
|
|
|
|
clauses(Meta, Clauses, S) ->
|
|
Catch = elixir_clauses:get_pairs(Meta, 'catch', Clauses, S),
|
|
Rescue = elixir_clauses:get_pairs(Meta, rescue, Clauses, S),
|
|
Transformer = fun(X, Acc) -> each_clause(Meta, X, umergec(S, Acc)) end,
|
|
lists:mapfoldl(Transformer, S, Rescue ++ Catch).
|
|
|
|
each_clause(Meta, { 'catch', Raw, Expr }, S) ->
|
|
{ Args, Guards } = elixir_clauses:extract_last_guards(Raw),
|
|
|
|
Final = case Args of
|
|
[X] -> [throw, X, { '_', Meta, nil }];
|
|
[X,Y] -> [X, Y, { '_', Meta, nil }];
|
|
[_,_,_] -> Args;
|
|
_ ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for catch")
|
|
end,
|
|
|
|
Condition = { '{}', Meta, Final },
|
|
elixir_clauses:assigns_block(?line(Meta), fun elixir_translator:translate_each/2, Condition, [Expr], Guards, S);
|
|
|
|
each_clause(Meta, { rescue, [Condition|T], Expr }, S) ->
|
|
case normalize_rescue(Meta, Condition, S) of
|
|
{ Left, Right } ->
|
|
case Left of
|
|
{ '_', _, _ } ->
|
|
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
|
{ Clause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
|
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, CS);
|
|
_ ->
|
|
{ Clause, Safe } = rescue_guards(Meta, Left, Right, S),
|
|
case Safe of
|
|
true ->
|
|
each_clause(Meta, { 'catch', [error, Clause|T], Expr }, S);
|
|
false ->
|
|
{ ClauseVar, CS } = elixir_scope:build_ex_var(?line(Meta), S),
|
|
{ FinalClause, _ } = rescue_guards(Meta, ClauseVar, Right, S),
|
|
Match = { '=', Meta, [
|
|
Left,
|
|
{ { '.', Meta, ['Elixir.Exception', normalize] }, Meta, [ClauseVar] }
|
|
] },
|
|
FinalExpr = prepend_to_block(Meta, Match, Expr),
|
|
each_clause(Meta, { 'catch', [error, FinalClause|T], FinalExpr }, CS)
|
|
end
|
|
end;
|
|
_ ->
|
|
each_clause(Meta, { 'catch', [error, Condition|T], Expr }, S)
|
|
end;
|
|
|
|
each_clause(Meta, {rescue,_,_}, S) ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "too many arguments given for rescue");
|
|
|
|
each_clause(Meta, {Key,_,_}, S) ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid key ~ts in try", [Key]).
|
|
|
|
%% Helpers
|
|
|
|
%% rescue [Error] -> _ in [Error]
|
|
normalize_rescue(Meta, List, S) when is_list(List) ->
|
|
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, List] }, S);
|
|
|
|
%% rescue _
|
|
normalize_rescue(_, { '_', _, Atom }, _S) when is_atom(Atom) ->
|
|
false;
|
|
|
|
%% rescue var -> var in _
|
|
normalize_rescue(_, { Name, Meta, Atom } = Rescue, S) when is_atom(Name), is_atom(Atom) ->
|
|
normalize_rescue(Meta, { in, Meta, [Rescue, { '_', Meta, nil }] }, S);
|
|
|
|
%% rescue var in [Exprs]
|
|
normalize_rescue(_, { in, Meta, [Left, Right] }, S) ->
|
|
case Right of
|
|
{ '_', _, _ } ->
|
|
{ Left, nil };
|
|
_ when is_list(Right) ->
|
|
is_valid_rescue_list(Right, S) andalso { Left, Right };
|
|
_ ->
|
|
Expanded = 'Elixir.Macro':expand(Right, elixir_scope:to_ex_env({ ?line(Meta), S })),
|
|
case is_valid_rescue_list(Expanded, S) of
|
|
true -> { Left, Expanded };
|
|
false ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid use of operator \"in\" in rescue inside try")
|
|
end
|
|
end;
|
|
|
|
normalize_rescue(Meta, Condition, S) ->
|
|
case elixir_translator:translate_each(Condition, S#elixir_scope{context=match}) of
|
|
{ { atom, _, Atom }, _ } ->
|
|
normalize_rescue(Meta, { in, Meta, [{ '_', Meta, nil }, [Atom]] }, S);
|
|
_ ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file, "invalid rescue clause. The clause should match on an alias, "
|
|
"a variable or be in the `var in [alias]` format")
|
|
end.
|
|
|
|
%% Convert rescue clauses into guards.
|
|
rescue_guards(_, Var, nil, _) -> { Var, false };
|
|
|
|
rescue_guards(Meta, Var, Guards, S) ->
|
|
{ RawElixir, RawErlang } = rescue_each_var(Meta, Var, Guards),
|
|
{ Elixir, Erlang, Safe } = rescue_each_ref(Meta, Var, Guards, RawElixir, RawErlang, RawErlang == [], S),
|
|
|
|
Final = case Elixir == [] of
|
|
true -> Erlang;
|
|
false ->
|
|
IsTuple = { is_tuple, Meta, [Var] },
|
|
IsException = { '==', Meta, [
|
|
{ { '.', Meta, [erlang, element] }, Meta, [2, Var] }, '__exception__'
|
|
] },
|
|
OrElse = join(Meta, 'or', Elixir),
|
|
[join(Meta, 'and', [IsTuple, IsException, OrElse])|Erlang]
|
|
end,
|
|
{
|
|
{ 'when', Meta, [Var, reverse_join(Meta, 'when', Final)] },
|
|
Safe
|
|
}.
|
|
|
|
%% Handle variables in the right side of rescue.
|
|
|
|
rescue_each_var(Meta, ClauseVar, Guards) ->
|
|
Vars = [Var || Var <- Guards, is_var(Var)],
|
|
|
|
case Vars == [] of
|
|
true -> { [], [] };
|
|
false ->
|
|
Elixir = [exception_compare(Meta, ClauseVar, Var) || Var <- Vars],
|
|
Erlang = lists:map(fun(Rescue) ->
|
|
Compares = [{ '==', Meta, [Rescue, Var] } || Var <- Vars],
|
|
{ 'and', Meta, [
|
|
erlang_rescue_guard_for(Meta, ClauseVar, Rescue),
|
|
join(Meta, 'or', Compares)
|
|
] }
|
|
end, erlang_rescues()),
|
|
{ Elixir, Erlang }
|
|
end.
|
|
|
|
%% Rescue each atom name considering their Erlang or Elixir matches.
|
|
%% Matching of variables is done with Erlang exceptions is done in another
|
|
%% method for optimization.
|
|
|
|
%% Ignore variables
|
|
rescue_each_ref(Meta, Var, [{ Name, _, Atom }|T], Elixir, Erlang, Safe, S) when is_atom(Name), is_atom(Atom) ->
|
|
rescue_each_ref(Meta, Var, T, Elixir, Erlang, Safe, S);
|
|
|
|
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, _Safe, S) when
|
|
H == 'Elixir.UndefinedFunctionError'; H == 'Elixir.ErlangError';
|
|
H == 'Elixir.ArgumentError'; H == 'Elixir.ArithmeticError';
|
|
H == 'Elixir.BadArityError'; H == 'Elixir.BadFunctionError';
|
|
H == 'Elixir.MatchError'; H == 'Elixir.CaseClauseError';
|
|
H == 'Elixir.FunctionClauseError'; H == 'Elixir.SystemLimitError' ->
|
|
Expr = { 'or', Meta, [
|
|
erlang_rescue_guard_for(Meta, Var, H),
|
|
exception_compare(Meta, Var, H)
|
|
] },
|
|
rescue_each_ref(Meta, Var, T, Elixir, [Expr|Erlang], false, S);
|
|
|
|
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) when is_atom(H) ->
|
|
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S);
|
|
|
|
rescue_each_ref(Meta, Var, [H|T], Elixir, Erlang, Safe, S) ->
|
|
case elixir_translator:translate_each(H, S) of
|
|
{ { atom, _, Atom }, _ } ->
|
|
rescue_each_ref(Meta, Var, [Atom|T], Elixir, Erlang, Safe, S);
|
|
_ ->
|
|
rescue_each_ref(Meta, Var, T, [exception_compare(Meta, Var, H)|Elixir], Erlang, Safe, S)
|
|
end;
|
|
|
|
rescue_each_ref(_, _, [], Elixir, Erlang, Safe, _) ->
|
|
{ Elixir, Erlang, Safe }.
|
|
|
|
%% Handle erlang rescue matches.
|
|
|
|
erlang_rescues() ->
|
|
[
|
|
'Elixir.UndefinedFunctionError', 'Elixir.ArgumentError', 'Elixir.ArithmeticError', 'Elixir.BadArityError',
|
|
'Elixir.BadFunctionError', 'Elixir.MatchError', 'Elixir.CaseClauseError', 'Elixir.FunctionClauseError',
|
|
'Elixir.SystemLimitError', 'Elixir.ErlangError'
|
|
].
|
|
|
|
erlang_rescue_guard_for(Meta, Var, List) when is_list(List) ->
|
|
join(Meta, 'or', [erlang_rescue_guard_for(Meta, Var, X) || X <- List]);
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.UndefinedFunctionError') ->
|
|
{ '==', Meta, [Var, undef] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.FunctionClauseError') ->
|
|
{ '==', Meta, [Var, function_clause] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.SystemLimitError') ->
|
|
{ '==', Meta, [Var, system_limit] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArithmeticError') ->
|
|
{ '==', Meta, [Var, badarith] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadArityError') ->
|
|
{ 'and', Meta, [
|
|
{ is_tuple, Meta, [Var] },
|
|
exception_compare(Meta, Var, badarity)
|
|
] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.BadFunctionError') ->
|
|
{ 'and', Meta, [
|
|
{ is_tuple, Meta, [Var] },
|
|
exception_compare(Meta, Var, badfun)
|
|
] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.MatchError') ->
|
|
{ 'and', Meta, [
|
|
{ is_tuple, Meta, [Var] },
|
|
exception_compare(Meta, Var, badmatch)
|
|
] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.CaseClauseError') ->
|
|
{ 'and', Meta, [
|
|
{ is_tuple, Meta, [Var] },
|
|
exception_compare(Meta, Var, case_clause)
|
|
] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.ArgumentError') ->
|
|
{ 'or', Meta, [
|
|
{ '==', Meta, [Var, badarg] },
|
|
{ 'and', Meta, [
|
|
{ is_tuple, Meta, [Var] },
|
|
exception_compare(Meta, Var, badarg)
|
|
] }
|
|
] };
|
|
|
|
erlang_rescue_guard_for(Meta, Var, 'Elixir.ErlangError') ->
|
|
IsNotTuple = { 'not', Meta, [{ is_tuple, Meta, [Var] }] },
|
|
IsException = { '!=', Meta, [
|
|
{ { '.', Meta, [ erlang, element ] }, Meta, [2, Var] }, '__exception__'
|
|
] },
|
|
{ 'or', Meta, [IsNotTuple, IsException] }.
|
|
|
|
%% Helpers
|
|
|
|
format_error({ rescue_no_match, Var, Alias }) ->
|
|
VarBinary = 'Elixir.Macro':to_binary(Var),
|
|
AliasBinary = 'Elixir.Macro':to_binary(Alias),
|
|
Message = "rescue clause (~ts = ~ts) can never match, maybe you meant to write: ~ts in [~ts] ?",
|
|
io_lib:format(Message, [AliasBinary, VarBinary, VarBinary, AliasBinary]).
|
|
|
|
is_var({ Name, _, Atom }) when is_atom(Name), is_atom(Atom) -> true;
|
|
is_var(_) -> false.
|
|
|
|
is_erl_var_or_atom({ Kind, Line, Name }) when Kind == var orelse Kind == atom, is_integer(Line), is_atom(Name) -> true;
|
|
is_erl_var_or_atom(_) -> false.
|
|
|
|
is_valid_rescue_list(Right, S) when is_list(Right) ->
|
|
{ TRight, _ } = elixir_translator:translate(Right, S),
|
|
lists:all(fun is_erl_var_or_atom/1, TRight).
|
|
|
|
exception_compare(Meta, Var, Expr) ->
|
|
{ '==', Meta, [
|
|
{ { '.', Meta, [ erlang, element ] }, Meta, [1, Var] },
|
|
Expr
|
|
] }.
|
|
|
|
join(Meta, Kind, [H|T]) ->
|
|
lists:foldl(fun(X, Acc) -> { Kind, Meta, [Acc, X] } end, H, T).
|
|
|
|
reverse_join(Meta, Kind, [H|T]) ->
|
|
lists:foldl(fun(X, Acc) -> { Kind, Meta, [X, Acc] } end, H, T).
|
|
|
|
prepend_to_block(_Meta, Expr, { '__block__', Meta, Args }) ->
|
|
{ '__block__', Meta, [Expr|Args] };
|
|
|
|
prepend_to_block(Meta, Expr, Args) ->
|
|
{ '__block__', Meta, [Expr, Args] }. |