335 lines
12 KiB
Erlang
335 lines
12 KiB
Erlang
%% Main entry point for translations. All macros that cannot be
|
|
%% overriden are defined in this file.
|
|
-module(elixir_translator).
|
|
-export([translate_many/2, translate/2, translate_arg/3, translate_args/2]).
|
|
-import(elixir_scope, [mergev/2, mergec/2]).
|
|
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
|
-include("elixir.hrl").
|
|
|
|
translate_many(Forms, S) ->
|
|
lists:mapfoldl(fun translate/2, S, Forms).
|
|
|
|
%% =
|
|
|
|
translate({ '=', Meta, [Left, Right] }, S) ->
|
|
{ TRight, SR } = translate(Right, S),
|
|
{ TLeft, SL } = elixir_clauses:match(fun translate/2, Left, SR),
|
|
{ { match, ?line(Meta), TLeft, TRight }, SL };
|
|
|
|
%% Containers
|
|
|
|
translate({ '{}', Meta, Args }, S) when is_list(Args) ->
|
|
{ TArgs, SE } = translate_args(Args, S),
|
|
{ { tuple, ?line(Meta), TArgs }, SE };
|
|
|
|
translate({ '<<>>', Meta, Args }, S) when is_list(Args) ->
|
|
elixir_bitstring:translate(Meta, Args, S);
|
|
|
|
%% Blocks and scope rewriters
|
|
|
|
translate({ '__block__', Meta, Args }, S) when is_list(Args) ->
|
|
{ TArgs, NS } = translate_many(Args, S),
|
|
{ { block, ?line(Meta), TArgs }, NS };
|
|
|
|
%% Erlang op
|
|
|
|
translate({ '__op__', Meta, [Op, Expr] }, S) when is_atom(Op) ->
|
|
{ TExpr, NS } = translate(Expr, S),
|
|
{ { op, ?line(Meta), Op, TExpr }, NS };
|
|
|
|
translate({ '__op__', Meta, [Op, Left, Right] }, S) when is_atom(Op) ->
|
|
{ [TLeft, TRight], NS } = translate_args([Left, Right], S),
|
|
{ { op, ?line(Meta), Op, TLeft, TRight }, NS };
|
|
|
|
%% Lexical
|
|
|
|
translate({ Lexical, _, [_, _] }, S) when Lexical == import; Lexical == alias; Lexical == require ->
|
|
{ { atom, 0, nil }, S };
|
|
|
|
%% Pseudo variables
|
|
|
|
translate({ '__CALLER__', Meta, Atom }, S) when is_atom(Atom) ->
|
|
{ { var, ?line(Meta), '__CALLER__' }, S#elixir_scope{caller=true} };
|
|
|
|
%% Functions
|
|
|
|
translate({ '&', Meta, [{ '/', [], [{ Fun, [], Atom }, Arity] }] }, S)
|
|
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
|
|
{ { 'fun', ?line(Meta), { function, Fun, Arity } }, S };
|
|
translate({ '&', Meta, [Arg] }, S) when is_integer(Arg) ->
|
|
compile_error(Meta, S#elixir_scope.file, "unhandled &~B outside of a capture", [Arg]);
|
|
|
|
translate({ fn, Meta, Clauses }, S) ->
|
|
elixir_fn:translate(Meta, Clauses, S);
|
|
|
|
%% Case
|
|
|
|
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
|
Clauses = elixir_clauses:get_pairs(do, KV),
|
|
{ TExpr, NS } = translate(Expr, S),
|
|
|
|
% RClauses = case elixir_utils:returns_boolean(TExpr) of
|
|
% true -> rewrite_case_clauses(Clauses);
|
|
% false -> Clauses
|
|
% end,
|
|
|
|
{ TClauses, TS } = elixir_clauses:clauses(Meta, Clauses, NS),
|
|
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
|
|
|
%% Try
|
|
|
|
translate({'try', Meta, [Clauses]}, RS) when is_list(Clauses) ->
|
|
S = RS#elixir_scope{noname=true},
|
|
Do = proplists:get_value('do', Clauses, nil),
|
|
{ TDo, SB } = elixir_translator:translate(Do, S),
|
|
|
|
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
|
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, mergec(S, SB)),
|
|
|
|
After = proplists:get_value('after', Clauses, nil),
|
|
{ TAfter, SA } = translate(After, mergec(S, SC)),
|
|
|
|
Else = elixir_clauses:get_pairs(else, Clauses),
|
|
{ TElse, SE } = elixir_clauses:clauses(Meta, Else, mergec(S, SA)),
|
|
|
|
SF = (mergec(S, SE))#elixir_scope{noname=RS#elixir_scope.noname},
|
|
{ { 'try', ?line(Meta), unblock(TDo), TElse, TCatch, unblock(TAfter) }, SF };
|
|
|
|
%% Receive
|
|
|
|
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
|
Do = elixir_clauses:get_pairs(do, KV, true),
|
|
|
|
case lists:keyfind('after', 1, KV) of
|
|
false ->
|
|
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do, S),
|
|
{ { 'receive', ?line(Meta), TClauses }, SC };
|
|
_ ->
|
|
After = elixir_clauses:get_pairs('after', KV),
|
|
{ TClauses, SC } = elixir_clauses:clauses(Meta, Do ++ After, S),
|
|
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
|
{ _, _, [FExpr], _, FAfter } = TAfter,
|
|
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
|
end;
|
|
|
|
%% Comprehensions
|
|
|
|
translate({ Kind, Meta, Args }, S) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
|
translate_comprehension(Meta, Kind, Args, S);
|
|
|
|
%% Super
|
|
|
|
translate({ super, Meta, Args }, S) when is_list(Args) ->
|
|
Module = assert_module_scope(Meta, super, S),
|
|
Function = assert_function_scope(Meta, super, S),
|
|
elixir_def_overridable:ensure_defined(Meta, Module, Function, S),
|
|
|
|
{ _, Arity } = Function,
|
|
|
|
{ TArgs, TS } = if
|
|
length(Args) == Arity ->
|
|
translate_args(Args, S);
|
|
true ->
|
|
compile_error(Meta, S#elixir_scope.file, "super must be called with the same number of "
|
|
"arguments as the current function")
|
|
end,
|
|
|
|
Super = elixir_def_overridable:name(Module, Function),
|
|
{ { call, ?line(Meta), { atom, ?line(Meta), Super }, TArgs }, TS#elixir_scope{super=true} };
|
|
|
|
%% Variables
|
|
|
|
translate({ '^', Meta, [ { Name, VarMeta, Kind } = Var ] },
|
|
#elixir_scope{extra=fn_match, extra_guards=Extra} = S) when is_atom(Name), is_atom(Kind) ->
|
|
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
|
{ ok, Value } ->
|
|
Line = ?line(Meta),
|
|
{ TVar, TS } = translate(Var, S),
|
|
Guard = { op, Line, '=:=', { var, ?line(Meta), Value }, TVar },
|
|
{ TVar, TS#elixir_scope{extra_guards=[Guard|Extra]} };
|
|
error ->
|
|
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
|
end;
|
|
|
|
translate({ '^', Meta, [ { Name, VarMeta, Kind } ] }, #elixir_scope{context=match} = S) when is_atom(Name), is_atom(Kind) ->
|
|
case orddict:find({ Name, var_kind(VarMeta, Kind) }, S#elixir_scope.backup_vars) of
|
|
{ ok, Value } ->
|
|
{ { var, ?line(Meta), Value }, S };
|
|
error ->
|
|
compile_error(Meta, S#elixir_scope.file, "unbound variable ^~ts", [Name])
|
|
end;
|
|
|
|
translate({ '_', Meta, Kind }, #elixir_scope{context=match} = S) when is_atom(Kind) ->
|
|
{ { var, ?line(Meta), '_' }, S };
|
|
|
|
translate({ '_', Meta, Kind }, S) when is_atom(Kind) ->
|
|
compile_error(Meta, S#elixir_scope.file, "unbound variable _");
|
|
|
|
translate({ Name, Meta, Kind }, S) when is_atom(Name), is_atom(Kind) ->
|
|
elixir_scope:translate_var(Meta, Name, var_kind(Meta, Kind), S);
|
|
|
|
%% Local calls
|
|
|
|
translate({ Name, Meta, Args }, S) when is_atom(Name), is_list(Meta), is_list(Args) ->
|
|
if
|
|
S#elixir_scope.context == match ->
|
|
compile_error(Meta, S#elixir_scope.file,
|
|
"cannot invoke function ~ts/~B inside match", [Name, length(Args)]);
|
|
S#elixir_scope.context == guard ->
|
|
Arity = length(Args),
|
|
File = S#elixir_scope.file,
|
|
case Arity of
|
|
0 -> compile_error(Meta, File, "unknown variable ~ts or cannot invoke "
|
|
"function ~ts/~B inside guard", [Name, Name, Arity]);
|
|
_ -> compile_error(Meta, File, "cannot invoke local ~ts/~B inside guard",
|
|
[Name, Arity])
|
|
end;
|
|
S#elixir_scope.function == nil ->
|
|
compile_error(Meta, S#elixir_scope.file, "undefined function ~ts/~B", [Name, length(Args)]);
|
|
true ->
|
|
Line = ?line(Meta),
|
|
{ TArgs, NS } = translate_args(Args, S),
|
|
{ { call, Line, { atom, Line, Name }, TArgs }, NS }
|
|
end;
|
|
|
|
%% Remote calls
|
|
|
|
translate({ { '.', _, [Left, Right] }, Meta, Args }, S)
|
|
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
|
{ TLeft, SL } = translate(Left, S),
|
|
{ TRight, SR } = translate(Right, mergec(S, SL)),
|
|
|
|
case S#elixir_scope.context of
|
|
Context when Left /= erlang, (Context == match) orelse (Context == guard) ->
|
|
compile_error(Meta, S#elixir_scope.file, "cannot invoke remote function ~ts.~ts/~B inside ~ts",
|
|
['Elixir.Macro':to_string(Left), Right, length(Args), Context]);
|
|
_ ->
|
|
Line = ?line(Meta),
|
|
{ TArgs, SA } = translate_args(Args, mergec(S, SR)),
|
|
{ { call, Line, { remote, Line, TLeft, TRight }, TArgs }, mergev(SL, mergev(SR, SA)) }
|
|
end;
|
|
|
|
%% Anonymous function calls
|
|
|
|
translate({ { '.', _, [Expr] }, Meta, Args }, S) when is_list(Args) ->
|
|
{ TExpr, SE } = translate(Expr, S),
|
|
{ TArgs, SA } = translate_args(Args, mergec(S, SE)),
|
|
{ { call, ?line(Meta), TExpr, TArgs }, mergev(SE, SA) };
|
|
|
|
%% Literals
|
|
|
|
translate(List, S) when is_list(List) ->
|
|
Fun = case S#elixir_scope.context of
|
|
match -> fun translate/2;
|
|
_ -> fun(X, Acc) -> translate_arg(X, Acc, S) end
|
|
end,
|
|
translate_list(List, Fun, S, []);
|
|
|
|
translate({ Left, Right }, S) ->
|
|
{ TArgs, SE } = translate_args([Left, Right], S),
|
|
{ { tuple, 0, TArgs }, SE };
|
|
|
|
translate(Other, S) ->
|
|
{ elixir_utils:elixir_to_erl(Other), S }.
|
|
|
|
%% Helpers
|
|
|
|
translate_list([{ '|', _, [_, _]=Args}], Fun, Acc, List) ->
|
|
{ [TLeft,TRight], TAcc } = lists:mapfoldl(Fun, Acc, Args),
|
|
{ build_list([TLeft|List], TRight), TAcc };
|
|
translate_list([H|T], Fun, Acc, List) ->
|
|
{ TH, TAcc } = Fun(H, Acc),
|
|
translate_list(T, Fun, TAcc, [TH|List]);
|
|
translate_list([], _Fun, Acc, List) ->
|
|
{ build_list(List, { nil, 0 }), Acc }.
|
|
|
|
build_list([H|T], Acc) ->
|
|
build_list(T, { cons, 0, H, Acc });
|
|
build_list([], Acc) ->
|
|
Acc.
|
|
|
|
var_kind(Meta, Kind) ->
|
|
case lists:keyfind(counter, 1, Meta) of
|
|
{ counter, Counter } -> Counter;
|
|
false -> Kind
|
|
end.
|
|
|
|
%% Case
|
|
|
|
%% TODO: Once we have elixir_exp, we can move this
|
|
%% clause to Elixir code and out of case.
|
|
rewrite_case_clauses([
|
|
{do,Meta1,[{'when',_,[{V,M,C},{in,_,[{V,M,C},[false,nil]]}]}],False},
|
|
{do,Meta2,[{'_',_,UC}],True}] = Clauses)
|
|
when is_atom(V), is_list(M), is_atom(C), is_atom(UC) ->
|
|
case lists:keyfind('cond', 1, M) of
|
|
{ 'cond', true } ->
|
|
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
|
_ ->
|
|
Clauses
|
|
end;
|
|
rewrite_case_clauses(Clauses) ->
|
|
Clauses.
|
|
|
|
%% Pack a list of expressions from a block.
|
|
unblock({ 'block', _, Exprs }) -> Exprs;
|
|
unblock(Expr) -> [Expr].
|
|
|
|
%% Translate args
|
|
|
|
translate_arg(Arg, Acc, S) ->
|
|
{ TArg, TAcc } = translate(Arg, mergec(S, Acc)),
|
|
{ TArg, mergev(Acc, TAcc) }.
|
|
|
|
translate_args(Args, #elixir_scope{context=match} = S) ->
|
|
translate_many(Args, S);
|
|
|
|
translate_args(Args, S) ->
|
|
lists:mapfoldl(fun(X, Acc) -> translate_arg(X, Acc, S) end, S, Args).
|
|
|
|
%% Comprehensions
|
|
|
|
translate_comprehension(Meta, Kind, Args, S) ->
|
|
{ Cases, [{do,Expr}] } = elixir_utils:split_last(Args),
|
|
{ TCases, SC } = lists:mapfoldl(fun(C, Acc) -> translate_comprehension_clause(Meta, C, Acc) end, S, Cases),
|
|
{ TExpr, SE } = translate_comprehension_do(Meta, Kind, Expr, SC),
|
|
{ { Kind, ?line(Meta), TExpr, TCases }, mergec(S, SE) }.
|
|
|
|
translate_comprehension_do(_Meta, bc, { '<<>>', _, _ } = Expr, S) ->
|
|
translate(Expr, S);
|
|
|
|
translate_comprehension_do(Meta, bc, _Expr, S) ->
|
|
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be returned");
|
|
|
|
translate_comprehension_do(_Meta, _Kind, Expr, S) ->
|
|
translate(Expr, S).
|
|
|
|
translate_comprehension_clause(_Meta, {inbits, Meta, [{ '<<>>', _, _} = Left, Right]}, S) ->
|
|
{ TRight, SR } = translate(Right, S),
|
|
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
|
{ { b_generate, ?line(Meta), TLeft, TRight }, SL };
|
|
|
|
translate_comprehension_clause(_Meta, {inbits, Meta, [_Left, _Right]}, S) ->
|
|
compile_error(Meta, S#elixir_scope.file, "a bit comprehension expects a bit string << >> to be used in inbits generators");
|
|
|
|
translate_comprehension_clause(_Meta, {inlist, Meta, [Left, Right]}, S) ->
|
|
{ TRight, SR } = translate(Right, S),
|
|
{ TLeft, SL } = elixir_clauses:match(fun elixir_translator:translate/2, Left, SR),
|
|
{ { generate, ?line(Meta), TLeft, TRight }, SL };
|
|
|
|
translate_comprehension_clause(Meta, X, S) ->
|
|
Line = ?line(Meta),
|
|
{ TX, TS } = translate(X, S),
|
|
{ BX, BS } = elixir_utils:convert_to_boolean(Line, TX, true, TS),
|
|
{ { match, Line, { var, Line, '_' }, BX }, BS }.
|
|
|
|
%% Assertions
|
|
|
|
assert_module_scope(Meta, Kind, #elixir_scope{module=nil,file=File}) ->
|
|
compile_error(Meta, File, "cannot invoke ~ts outside module", [Kind]);
|
|
assert_module_scope(_Meta, _Kind, #elixir_scope{module=Module}) -> Module.
|
|
|
|
assert_function_scope(Meta, Kind, #elixir_scope{function=nil,file=File}) ->
|
|
compile_error(Meta, File, "cannot invoke ~ts outside function", [Kind]);
|
|
assert_function_scope(_Meta, _Kind, #elixir_scope{function=Function}) -> Function.
|