333 lines
12 KiB
Erlang
333 lines
12 KiB
Erlang
%% Those macros behave like they belong to Kernel,
|
|
%% but do not since they need to be implemented in Erlang.
|
|
-module(elixir_macros).
|
|
-export([translate/2]).
|
|
-import(elixir_translator, [translate_each/2, translate_args/2, translate_apply/7]).
|
|
-import(elixir_scope, [umergec/2, umergea/2]).
|
|
-import(elixir_errors, [compile_error/3, syntax_error/3, syntax_error/4,
|
|
assert_no_function_scope/3, assert_module_scope/3, assert_no_match_or_guard_scope/3]).
|
|
|
|
-include("elixir.hrl").
|
|
-define(opt_in_types(Kind), Kind == atom orelse Kind == integer orelse Kind == float).
|
|
|
|
%% Operators
|
|
|
|
translate({ '+', _Meta, [Expr] }, S) when is_number(Expr) ->
|
|
translate_each(Expr, S);
|
|
|
|
translate({ '-', _Meta, [Expr] }, S) when is_number(Expr) ->
|
|
translate_each(-1 * Expr, S);
|
|
|
|
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
|
Op == '<-' orelse Op == '--' ->
|
|
assert_no_match_or_guard_scope(Meta, Op, S),
|
|
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
|
|
|
translate({ Op, Meta, Exprs }, S) when is_list(Exprs),
|
|
Op == '+' orelse Op == '-' orelse Op == '*' orelse Op == '/' orelse
|
|
Op == '++' orelse Op == 'not' orelse Op == 'and' orelse Op == 'or' orelse
|
|
Op == 'xor' orelse Op == '<' orelse Op == '>' orelse Op == '<=' orelse
|
|
Op == '>=' orelse Op == '==' orelse Op == '!=' orelse Op == '===' orelse
|
|
Op == '!==' ->
|
|
translate_each({ '__op__', Meta, [Op|Exprs] }, S);
|
|
|
|
translate({ '!', Meta, [{ '!', _, [Expr] }] }, S) ->
|
|
{ TExpr, SE } = translate_each(Expr, S),
|
|
elixir_utils:convert_to_boolean(?line(Meta), TExpr, true, S#elixir_scope.context == guard, SE);
|
|
|
|
translate({ '!', Meta, [Expr] }, S) ->
|
|
{ TExpr, SE } = translate_each(Expr, S),
|
|
elixir_utils:convert_to_boolean(?line(Meta), TExpr, false, S#elixir_scope.context == guard, SE);
|
|
|
|
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=nil} = S) ->
|
|
{ _, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
|
{ TExpr, TS };
|
|
|
|
translate({ in, Meta, [Left, Right] }, #elixir_scope{extra_guards=Extra} = S) ->
|
|
{ TVar, TExpr, TS } = translate_in(Meta, Left, Right, S),
|
|
{ TVar, TS#elixir_scope{extra_guards=[TExpr|Extra]} };
|
|
|
|
%% @
|
|
|
|
translate({'@', Meta, [{ Name, _, [Arg] }]}, S) when Name == typep; Name == type; Name == spec; Name == callback; Name == opaque ->
|
|
case elixir_compiler:get_opt(internal) of
|
|
true -> { { nil, ?line(Meta) }, S };
|
|
false ->
|
|
Call = { { '.', Meta, ['Elixir.Kernel.Typespec', spec_to_macro(Name)] }, Meta, [Arg] },
|
|
translate_each(Call, S)
|
|
end;
|
|
|
|
translate({'@', Meta, [{ Name, _, Args }]}, S) ->
|
|
assert_module_scope(Meta, '@', S),
|
|
|
|
case is_reserved_data(Name) andalso elixir_compiler:get_opt(internal) of
|
|
true ->
|
|
{ { nil, ?line(Meta) }, S };
|
|
_ ->
|
|
case Args of
|
|
[Arg] ->
|
|
case S#elixir_scope.function of
|
|
nil ->
|
|
translate_each({
|
|
{ '.', Meta, ['Elixir.Module', put_attribute] },
|
|
Meta,
|
|
[ { '__MODULE__', Meta, false }, Name, Arg ]
|
|
}, S);
|
|
_ ->
|
|
syntax_error(Meta, S#elixir_scope.file,
|
|
"cannot dynamically set attribute @~ts inside a function", [Name])
|
|
end;
|
|
_ when is_atom(Args) or (Args == []) ->
|
|
case S#elixir_scope.function of
|
|
nil ->
|
|
translate_each({
|
|
{ '.', Meta, ['Elixir.Module', get_attribute] },
|
|
Meta,
|
|
[ { '__MODULE__', Meta, false }, Name, true ]
|
|
}, S);
|
|
_ ->
|
|
Contents = 'Elixir.Module':get_attribute(S#elixir_scope.module, Name),
|
|
{ elixir_utils:elixir_to_erl(Contents), S }
|
|
end;
|
|
_ ->
|
|
syntax_error(Meta, S#elixir_scope.file, "expected 0 or 1 argument for @~ts, got: ~p", [Name, length(Args)])
|
|
end
|
|
end;
|
|
|
|
%% Case
|
|
|
|
translate({'case', Meta, [Expr, KV]}, S) when is_list(KV) ->
|
|
assert_no_match_or_guard_scope(Meta, 'case', S),
|
|
Clauses = elixir_clauses:get_pairs(Meta, do, KV, S),
|
|
{ TExpr, NS } = translate_each(Expr, S),
|
|
|
|
RClauses = case elixir_utils:returns_boolean(TExpr) of
|
|
true -> rewrite_case_clauses(Clauses);
|
|
false -> Clauses
|
|
end,
|
|
|
|
{ TClauses, TS } = elixir_clauses:match(Meta, RClauses, NS),
|
|
{ { 'case', ?line(Meta), TExpr, TClauses }, TS };
|
|
|
|
%% Try
|
|
|
|
translate({'try', Meta, [Clauses]}, S) when is_list(Clauses) ->
|
|
assert_no_match_or_guard_scope(Meta, 'try', S),
|
|
|
|
Do = proplists:get_value('do', Clauses, nil),
|
|
{ TDo, SB } = elixir_translator:translate_each(Do, S#elixir_scope{noname=true}),
|
|
|
|
Catch = [Tuple || { X, _ } = Tuple <- Clauses, X == 'rescue' orelse X == 'catch'],
|
|
{ TCatch, SC } = elixir_try:clauses(Meta, Catch, umergea(S, SB)),
|
|
|
|
After = proplists:get_value('after', Clauses, nil),
|
|
{ TAfter, SA } = elixir_translator:translate_each(After, umergea(S, SC)),
|
|
|
|
Else = elixir_clauses:get_pairs(Meta, else, Clauses, S),
|
|
{ TElse, SE } = elixir_clauses:match(Meta, Else, umergea(S, SA)),
|
|
|
|
SF = (umergec(S, SE))#elixir_scope{noname=S#elixir_scope.noname},
|
|
{ { 'try', ?line(Meta), pack(TDo), TElse, TCatch, pack(TAfter) }, SF };
|
|
|
|
%% Receive
|
|
|
|
translate({'receive', Meta, [KV] }, S) when is_list(KV) ->
|
|
assert_no_match_or_guard_scope(Meta, 'receive', S),
|
|
Do = elixir_clauses:get_pairs(Meta, do, KV, S, true),
|
|
|
|
case lists:keyfind('after', 1, KV) of
|
|
false ->
|
|
{ TClauses, SC } = elixir_clauses:match(Meta, Do, S),
|
|
{ { 'receive', ?line(Meta), TClauses }, SC };
|
|
_ ->
|
|
After = elixir_clauses:get_pairs(Meta, 'after', KV, S),
|
|
{ TClauses, SC } = elixir_clauses:match(Meta, Do ++ After, S),
|
|
{ FClauses, TAfter } = elixir_utils:split_last(TClauses),
|
|
{ _, _, [FExpr], _, FAfter } = TAfter,
|
|
{ { 'receive', ?line(Meta), FClauses, FExpr, FAfter }, SC }
|
|
end;
|
|
|
|
%% Definitions
|
|
|
|
translate({defmodule, Meta, [Ref, KV]}, S) when is_list(KV) ->
|
|
{ TRef, _ } = translate_each(Ref, S),
|
|
|
|
Block = case lists:keyfind(do, 1, KV) of
|
|
{ do, DoValue } -> DoValue;
|
|
false -> syntax_error(Meta, S#elixir_scope.file, "missing do keyword in defmodule")
|
|
end,
|
|
|
|
{ FRef, FS } = case TRef of
|
|
{ atom, _, Module } ->
|
|
FullModule = expand_module(Ref, Module, S),
|
|
|
|
RS = case elixir_aliases:nesting_alias(S#elixir_scope.module, FullModule) of
|
|
{ New, Old } -> elixir_aliases:store(Meta, New, Old, S);
|
|
false -> S
|
|
end,
|
|
|
|
{
|
|
{ atom, Meta, FullModule },
|
|
RS#elixir_scope{context_modules=[FullModule|S#elixir_scope.context_modules]}
|
|
};
|
|
_ ->
|
|
{ TRef, S }
|
|
end,
|
|
|
|
MS = FS#elixir_scope{local=nil},
|
|
{ elixir_module:translate(Meta, FRef, Block, MS), FS };
|
|
|
|
translate({Kind, Meta, [Call]}, S) when ?defs(Kind) ->
|
|
translate({Kind, Meta, [Call, nil]}, S);
|
|
|
|
translate({Kind, Meta, [Call, Expr]}, S) when ?defs(Kind) ->
|
|
assert_module_scope(Meta, Kind, S),
|
|
assert_no_function_scope(Meta, Kind, S),
|
|
{ TCall, QC, SC } = elixir_quote:erl_escape(Call, true, S),
|
|
{ TExpr, QE, SE } = elixir_quote:erl_escape(Expr, true, SC),
|
|
CheckClauses = (not lists:keymember(context, 1, Meta)) andalso
|
|
(not QC#elixir_quote.unquoted) andalso (not QE#elixir_quote.unquoted),
|
|
{ elixir_def:wrap_definition(Kind, Meta, TCall, TExpr, CheckClauses, SE), SE };
|
|
|
|
translate({Kind, Meta, [Name, Args, Guards, Expr]}, S) when ?defs(Kind) ->
|
|
assert_module_scope(Meta, Kind, S),
|
|
assert_no_function_scope(Meta, Kind, S),
|
|
{ TName, SN } = translate_each(Name, S),
|
|
{ TArgs, SA } = translate_each(Args, SN),
|
|
{ TGuards, SG } = translate_each(Guards, SA),
|
|
{ TExpr, SE } = translate_each(Expr, SG),
|
|
{ elixir_def:wrap_definition(Kind, Meta, TName, TArgs, TGuards, TExpr, false, SE), SE };
|
|
|
|
%% Apply - Optimize apply by checking what doesn't need to be dispatched dynamically
|
|
|
|
translate({ apply, Meta, [Left, Right, Args] }, S) when is_list(Args) ->
|
|
{ TLeft, SL } = translate_each(Left, S),
|
|
{ TRight, SR } = translate_each(Right, umergec(S, SL)),
|
|
translate_apply(Meta, TLeft, TRight, Args, S, SL, SR);
|
|
|
|
translate({ apply, Meta, Args }, S) ->
|
|
{ TArgs, NS } = translate_args(Args, S),
|
|
{ ?wrap_call(?line(Meta), erlang, apply, TArgs), NS };
|
|
|
|
translate({ Name, Meta, Args }, S) ->
|
|
syntax_error(Meta, S#elixir_scope.file,
|
|
"invalid arguments for macro ~ts/~B", [Name, length(Args)]).
|
|
|
|
%% Helpers
|
|
|
|
translate_in(Meta, Left, Right, S) ->
|
|
Line = ?line(Meta),
|
|
|
|
{ TLeft, SL } = case Left of
|
|
{ '_', _, Atom } when is_atom(Atom) ->
|
|
elixir_scope:build_erl_var(Line, S);
|
|
_ ->
|
|
translate_each(Left, S)
|
|
end,
|
|
|
|
{ TRight, SR } = translate_each(Right, SL),
|
|
|
|
Cache = (S#elixir_scope.context == nil),
|
|
|
|
{ Var, SV } = case Cache of
|
|
true -> elixir_scope:build_erl_var(Line, SR);
|
|
false -> { TLeft, SR }
|
|
end,
|
|
|
|
{ TCache, TExpr } = case TRight of
|
|
{ nil, _ } ->
|
|
Expr = { atom, Line, false },
|
|
{ Cache, Expr };
|
|
{ cons, _, _, _ } ->
|
|
[H|T] = elixir_utils:cons_to_list(TRight),
|
|
Expr = lists:foldr(fun(X, Acc) ->
|
|
{ op, Line, 'orelse', { op, Line, '==', Var, X }, Acc }
|
|
end, { op, Line, '==', Var, H }, T),
|
|
{ Cache, Expr };
|
|
{ string, _, [H|T] } ->
|
|
Expr = lists:foldl(fun(X, Acc) ->
|
|
{ op, Line, 'orelse', { op, Line, '==', Var, { integer, Line, X } }, Acc }
|
|
end, { op, Line, '==', Var, { integer, Line, H } }, T),
|
|
{ Cache, Expr };
|
|
{ tuple, _, [{ atom, _, 'Elixir.Range' }, Start, End] } ->
|
|
Expr = case { Start, End } of
|
|
{ { K1, _, StartInt }, { K2, _, EndInt } } when ?opt_in_types(K1), ?opt_in_types(K2), StartInt =< EndInt ->
|
|
increasing_compare(Line, Var, Start, End);
|
|
{ { K1, _, _ }, { K2, _, _ } } when ?opt_in_types(K1), ?opt_in_types(K2) ->
|
|
decreasing_compare(Line, Var, Start, End);
|
|
_ ->
|
|
{ op, Line, 'orelse',
|
|
{ op, Line, 'andalso',
|
|
{ op, Line, '=<', Start, End},
|
|
increasing_compare(Line, Var, Start, End) },
|
|
{ op, Line, 'andalso',
|
|
{ op, Line, '<', End, Start},
|
|
decreasing_compare(Line, Var, Start, End) } }
|
|
end,
|
|
{ Cache, Expr };
|
|
_ ->
|
|
case Cache of
|
|
true ->
|
|
{ false, ?wrap_call(Line, 'Elixir.Enum', 'member?', [TRight, TLeft]) };
|
|
false ->
|
|
compile_error(Meta, S#elixir_scope.file, "invalid args for operator in, it expects a "
|
|
"compile time list or range on the right side when used in guard expressions")
|
|
end
|
|
end,
|
|
|
|
case TCache of
|
|
true -> { Var, { block, Line, [ { match, Line, Var, TLeft }, TExpr ] }, SV };
|
|
false -> { Var, TExpr, SV }
|
|
end.
|
|
|
|
increasing_compare(Line, Var, Start, End) ->
|
|
{ op, Line, 'andalso',
|
|
{ op, Line, '>=', Var, Start },
|
|
{ op, Line, '=<', Var, End } }.
|
|
|
|
decreasing_compare(Line, Var, Start, End) ->
|
|
{ op, Line, 'andalso',
|
|
{ op, Line, '=<', Var, Start },
|
|
{ op, Line, '>=', Var, End } }.
|
|
|
|
rewrite_case_clauses([{do,Meta1,[{in,_,[{'_',_,_},[false,nil]]}],False},{do,Meta2,[{'_',_,_}],True}]) ->
|
|
[{do,Meta1,[false],False},{do,Meta2,[true],True}];
|
|
|
|
rewrite_case_clauses(Clauses) ->
|
|
Clauses.
|
|
|
|
%% defmodule :foo
|
|
expand_module(Raw, _Module, _S) when is_atom(Raw) ->
|
|
Raw;
|
|
|
|
%% defmodule Hello
|
|
expand_module({ '__aliases__', _, [H] }, _Module, S) ->
|
|
elixir_aliases:concat([S#elixir_scope.module, H]);
|
|
|
|
%% defmodule Hello.World
|
|
expand_module({ '__aliases__', _, _ } = Alias, Module, S) ->
|
|
case elixir_aliases:expand(Alias, S#elixir_scope.aliases, S#elixir_scope.macro_aliases) of
|
|
Atom when is_atom(Atom) ->
|
|
Module;
|
|
Aliases when is_list(Aliases) ->
|
|
elixir_aliases:concat([S#elixir_scope.module, Module])
|
|
end;
|
|
|
|
%% defmodule Elixir.Hello.World
|
|
expand_module(_Raw, Module, S) ->
|
|
elixir_aliases:concat([S#elixir_scope.module, Module]).
|
|
|
|
is_reserved_data(moduledoc) -> true;
|
|
is_reserved_data(doc) -> true;
|
|
is_reserved_data(_) -> false.
|
|
|
|
spec_to_macro(type) -> deftype;
|
|
spec_to_macro(typep) -> deftypep;
|
|
spec_to_macro(opaque) -> defopaque;
|
|
spec_to_macro(spec) -> defspec;
|
|
spec_to_macro(callback) -> defcallback.
|
|
|
|
% Pack a list of expressions from a block.
|
|
pack({ 'block', _, Exprs }) -> Exprs;
|
|
pack(Expr) -> [Expr].
|