550 lines
19 KiB
Erlang
550 lines
19 KiB
Erlang
-module(elixir_exp).
|
|
-export([expand/2, expand_many/2, expand_args/2, expand_arg/2]).
|
|
-import(elixir_errors, [compile_error/3, compile_error/4]).
|
|
-include("elixir.hrl").
|
|
|
|
%% =
|
|
|
|
expand({ '=', Meta, [Left, Right] }, E) ->
|
|
assert_no_guard_scope(Meta, '=', E),
|
|
{ ERight, ER } = expand(Right, E),
|
|
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, elixir_env:mergec(E, ER)),
|
|
{ { '=', Meta, [ELeft, ERight] }, elixir_env:mergevc(EL, ER) };
|
|
|
|
%% Literal operators
|
|
|
|
expand({ '{}', Meta, Args }, E) ->
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { '{}', Meta, EArgs }, EA };
|
|
|
|
expand({ '<<>>', Meta, Args }, E) ->
|
|
elixir_bitstring:expand(Meta, Args, E);
|
|
|
|
%% Other operators
|
|
|
|
expand({ '__op__', Meta, [_, _] = Args }, E) ->
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { '__op__', Meta, EArgs }, EA };
|
|
|
|
expand({ '__op__', Meta, [_, _, _] = Args }, E) ->
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { '__op__', Meta, EArgs }, EA };
|
|
|
|
expand({ '->', Meta, _Args }, E) ->
|
|
compile_error(Meta, E#elixir_env.file, "unhandled operator ->");
|
|
|
|
%% __block__
|
|
|
|
expand({ '__block__', _Meta, [] }, E) ->
|
|
{ nil, E };
|
|
expand({ '__block__', _Meta, [Arg] }, E) ->
|
|
expand(Arg, E);
|
|
expand({ '__block__', Meta, Args }, E) when is_list(Args) ->
|
|
{ EArgs, EA } = expand_many(Args, E),
|
|
{ { '__block__', Meta, EArgs }, EA };
|
|
|
|
%% __aliases__
|
|
|
|
expand({ '__aliases__', _, _ } = Alias, E) ->
|
|
case elixir_aliases:expand(Alias, E#elixir_env.aliases,
|
|
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker) of
|
|
Receiver when is_atom(Receiver) ->
|
|
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
|
{ Receiver, E };
|
|
Aliases ->
|
|
{ EAliases, EA } = expand_args(Aliases, E),
|
|
|
|
case lists:all(fun is_atom/1, EAliases) of
|
|
true ->
|
|
Receiver = elixir_aliases:concat(EAliases),
|
|
elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker),
|
|
{ Receiver, EA };
|
|
false ->
|
|
{ { { '.', [], [elixir_aliases, concat] }, [], EAliases }, EA }
|
|
end
|
|
end;
|
|
|
|
%% alias
|
|
|
|
expand({ alias, Meta, [Ref] }, E) ->
|
|
expand({ alias, Meta, [Ref,[]] }, E);
|
|
expand({ alias, Meta, [Ref, KV] }, E) ->
|
|
assert_no_match_or_guard_scope(Meta, alias, E),
|
|
{ ERef, ER } = expand(Ref, E),
|
|
{ EKV, ET } = expand_opts(Meta, alias, [as, warn], no_alias_opts(KV), ER),
|
|
|
|
if
|
|
is_atom(ERef) ->
|
|
{ { alias, Meta, [ERef, EKV] },
|
|
expand_alias(Meta, true, ERef, EKV, ET) };
|
|
true ->
|
|
compile_error(Meta, E#elixir_env.file,
|
|
"invalid arguments for alias, expected a compile time atom or alias as argument")
|
|
end;
|
|
|
|
expand({ require, Meta, [Ref] }, E) ->
|
|
expand({ require, Meta, [Ref, []] }, E);
|
|
expand({ require, Meta, [Ref, KV] }, E) ->
|
|
assert_no_match_or_guard_scope(Meta, require, E),
|
|
|
|
{ ERef, ER } = expand(Ref, E),
|
|
{ EKV, ET } = expand_opts(Meta, require, [as, warn], no_alias_opts(KV), ER),
|
|
|
|
if
|
|
is_atom(ERef) ->
|
|
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
|
{ { require, Meta, [ERef, EKV] },
|
|
expand_require(Meta, ERef, EKV, ET) };
|
|
true ->
|
|
compile_error(Meta, E#elixir_env.file,
|
|
"invalid arguments for require, expected a compile time atom or alias as argument")
|
|
end;
|
|
|
|
expand({ import, Meta, [Left] }, E) ->
|
|
expand({ import, Meta, [Left, []]}, E);
|
|
|
|
expand({ import, Meta, [Ref, KV] }, E) ->
|
|
assert_no_match_or_guard_scope(Meta, import, E),
|
|
{ ERef, ER } = expand(Ref, E),
|
|
{ EKV, ET } = expand_opts(Meta, import, [only, except, warn], KV, ER),
|
|
|
|
if
|
|
is_atom(ERef) ->
|
|
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
|
{ Functions, Macros } = elixir_import:import(Meta, ERef, EKV, ET),
|
|
{ { import, Meta, [ERef, EKV] },
|
|
expand_require(Meta, ERef, EKV, ET#elixir_env{functions=Functions, macros=Macros}) };
|
|
true ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid name for import, expected a compile time atom or alias")
|
|
end;
|
|
|
|
%% Pseudo vars
|
|
|
|
expand({ '__MODULE__', _, Atom }, E) when is_atom(Atom) ->
|
|
{ E#elixir_env.module, E };
|
|
expand({ '__FILE__', _, Atom }, E) when is_atom(Atom) ->
|
|
{ E#elixir_env.file, E };
|
|
expand({ '__DIR__', _, Atom }, E) when is_atom(Atom) ->
|
|
{ filename:dirname(E#elixir_env.file), E };
|
|
expand({ '__CALLER__', _, Atom } = Caller, E) when is_atom(Atom) ->
|
|
{ Caller, E };
|
|
expand({ '__ENV__', Meta, Atom }, E) when is_atom(Atom) ->
|
|
Env = elixir_env:env_to_ex({ ?line(Meta), E }),
|
|
{ { '{}', [], tuple_to_list(Env) }, E };
|
|
expand({ { '.', _, [{ '__ENV__', Meta, Atom }, Field] }, _, [] }, E) when is_atom(Atom), is_atom(Field) ->
|
|
{ (elixir_env:env_to_ex({ ?line(Meta), E })):Field(), E };
|
|
|
|
%% Quote
|
|
|
|
expand({ Unquote, Meta, [_] }, E) when Unquote == unquote; Unquote == unquote_splicing ->
|
|
compile_error(Meta, E#elixir_env.file, "~p called outside quote", [Unquote]);
|
|
|
|
expand({ quote, Meta, [Opts] }, E) when is_list(Opts) ->
|
|
case lists:keyfind(do, 1, Opts) of
|
|
{ do, Do } ->
|
|
expand({ quote, Meta, [lists:keydelete(do, 1, Opts), [{do,Do}]] }, E);
|
|
false ->
|
|
compile_error(Meta, E#elixir_env.file, "missing do keyword in quote")
|
|
end;
|
|
|
|
expand({ quote, Meta, [_] }, E) ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
|
|
|
expand({ quote, Meta, [KV, Do] }, E) when is_list(Do) ->
|
|
Exprs =
|
|
case lists:keyfind(do, 1, Do) of
|
|
{ do, Expr } -> Expr;
|
|
false -> compile_error(Meta, E#elixir_scope.file, "missing do keyword in quote")
|
|
end,
|
|
|
|
ValidOpts = [hygiene, context, var_context, location, line, unquote, bind_quoted],
|
|
{ EKV, ET } = expand_opts(Meta, quote, ValidOpts, KV, E),
|
|
|
|
Hygiene = case lists:keyfind(hygiene, 1, EKV) of
|
|
{ hygiene, List } when is_list(List) ->
|
|
List;
|
|
false ->
|
|
[]
|
|
end,
|
|
|
|
Context = case lists:keyfind(context, 1, EKV) of
|
|
{ context, Atom } when is_atom(Atom) ->
|
|
Atom;
|
|
{ context, _ } ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid :context for quote, expected a compile time atom or an alias");
|
|
false ->
|
|
case E#elixir_env.module of
|
|
nil -> 'Elixir';
|
|
Mod -> Mod
|
|
end
|
|
end,
|
|
|
|
Vars = lists:keyfind(vars, 1, Hygiene) /= { vars, false },
|
|
Aliases = lists:keyfind(aliases, 1, Hygiene) /= { aliases, false },
|
|
Imports = lists:keyfind(imports, 1, Hygiene) /= { imports, false },
|
|
|
|
Keep = lists:keyfind(location, 1, EKV) == { location, keep },
|
|
Line = proplists:get_value(line, EKV, false),
|
|
|
|
{ Binding, DefaultUnquote } = case lists:keyfind(bind_quoted, 1, EKV) of
|
|
{ bind_quoted, BQ } -> { BQ, false };
|
|
false -> { nil, true }
|
|
end,
|
|
|
|
Unquote = case lists:keyfind(unquote, 1, EKV) of
|
|
{ unquote, Bool } when is_boolean(Bool) -> Bool;
|
|
false -> DefaultUnquote
|
|
end,
|
|
|
|
Q = #elixir_quote{vars_hygiene=Vars, line=Line, keep=Keep, unquote=Unquote,
|
|
aliases_hygiene=Aliases, imports_hygiene=Imports, context=Context},
|
|
|
|
{ Quoted, _Q } = elixir_quote:quote(Exprs, Binding, Q, ET),
|
|
expand(Quoted, ET);
|
|
|
|
expand({ quote, Meta, [_, _] }, E) ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid arguments for quote");
|
|
|
|
%% Functions
|
|
|
|
expand({ '&', _, [Arg] } = Original, E) when is_integer(Arg) ->
|
|
{ Original, E };
|
|
expand({ '&', Meta, [Arg] }, E) ->
|
|
assert_no_match_or_guard_scope(Meta, '&', E),
|
|
case elixir_fn:capture(Meta, Arg, E) of
|
|
{ local, Fun, Arity } ->
|
|
{ { '&', Meta, [{ '/', [], [{ Fun, [], nil }, Arity] }] }, E };
|
|
{ expanded, Expr, EE } ->
|
|
expand(Expr, EE)
|
|
end;
|
|
|
|
expand({ fn, Meta, Pairs }, E) ->
|
|
assert_no_match_or_guard_scope(Meta, fn, E),
|
|
elixir_fn:expand(Meta, Pairs, E);
|
|
|
|
%% Case/Receive/Try
|
|
|
|
expand({'case', Meta, [Expr, KV]}, E) ->
|
|
assert_no_match_or_guard_scope(Meta, 'case', E),
|
|
{ EExpr, EE } = expand(Expr, E),
|
|
{ EClauses, EC } = elixir_exp_clauses:'case'(Meta, KV, EE),
|
|
{ { 'case', Meta, [EExpr, EClauses] }, EC };
|
|
|
|
expand({'receive', Meta, [KV]}, E) ->
|
|
assert_no_match_or_guard_scope(Meta, 'receive', E),
|
|
{ EClauses, EC } = elixir_exp_clauses:'receive'(Meta, KV, E),
|
|
{ { 'receive', Meta, [EClauses] }, EC };
|
|
|
|
expand({'try', Meta, [KV]}, E) ->
|
|
assert_no_match_or_guard_scope(Meta, 'try', E),
|
|
{ EClauses, EC } = elixir_exp_clauses:'try'(Meta, KV, E),
|
|
{ { 'try', Meta, [EClauses] }, EC };
|
|
|
|
%% Comprehensions
|
|
|
|
expand({ Kind, Meta, Args }, E) when is_list(Args), (Kind == lc) orelse (Kind == bc) ->
|
|
expand_comprehension(Meta, Kind, Args, E);
|
|
|
|
%% Super
|
|
|
|
expand({ super, Meta, Args }, E) when is_list(Args) ->
|
|
assert_no_match_or_guard_scope(Meta, super, E),
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { super, Meta, EArgs }, EA };
|
|
|
|
%% Vars
|
|
|
|
expand({ '^', Meta, [Arg] }, #elixir_env{context=match} = E) ->
|
|
case expand(Arg, E) of
|
|
{ { Name, _, Kind } = EArg, EA } when is_atom(Name), is_atom(Kind) ->
|
|
{ { '^', Meta, [EArg] }, EA };
|
|
_ ->
|
|
Msg = "invalid argument for unary operator ^, expected an existing variable, got: ^~ts",
|
|
compile_error(Meta, E#elixir_env.file, Msg, ['Elixir.Macro':to_string(Arg)])
|
|
end;
|
|
expand({ '^', Meta, [Arg] }, E) ->
|
|
compile_error(Meta, E#elixir_env.file,
|
|
"cannot use ^~ts outside of match clauses", ['Elixir.Macro':to_string(Arg)]);
|
|
|
|
expand({ '_', _, Kind } = Var, E) when is_atom(Kind) ->
|
|
{ Var, E };
|
|
expand({ Name, Meta, Kind } = Var, #elixir_env{context=match,vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
|
{ Var, E#elixir_env{vars=ordsets:add_element({ Name, var_kind(Meta, Kind) }, Vars)} };
|
|
expand({ Name, Meta, Kind } = Var, #elixir_env{vars=Vars} = E) when is_atom(Name), is_atom(Kind) ->
|
|
case lists:member({ Name, var_kind(Meta, Kind) }, Vars) of
|
|
true ->
|
|
{ Var, E };
|
|
false ->
|
|
VarMeta = lists:keyfind(var, 1, Meta),
|
|
if
|
|
VarMeta == { var, true } ->
|
|
Extra = case Kind of
|
|
nil -> "";
|
|
_ -> io_lib:format(" (context ~ts)", [elixir_aliases:inspect(Kind)])
|
|
end,
|
|
|
|
compile_error(Meta, E#elixir_env.file, "expected var ~ts~ts to expand to an existing "
|
|
"variable or be a part of a match", [Name, Extra]);
|
|
E#elixir_env.context == guard ->
|
|
compile_error(Meta, E#elixir_env.file, "unknown variable ~ts or cannot invoke "
|
|
"function ~ts/0 inside guard", [Name, Name]);
|
|
true ->
|
|
expand({ Name, Meta, [] }, E)
|
|
end
|
|
end;
|
|
|
|
%% Local calls
|
|
|
|
expand({ Atom, Meta, Args }, E) when is_atom(Atom), is_list(Meta), is_list(Args) ->
|
|
assert_no_ambiguous_op(Atom, Meta, Args, E),
|
|
|
|
elixir_dispatch:dispatch_import(Meta, Atom, Args, E, fun() ->
|
|
expand_local(Meta, Atom, Args, E)
|
|
end);
|
|
|
|
%% Remote calls
|
|
|
|
expand({ { '.', DotMeta, [Left, Right] }, Meta, Args }, E)
|
|
when (is_tuple(Left) orelse is_atom(Left)), is_atom(Right), is_list(Meta), is_list(Args) ->
|
|
{ ELeft, EL } = expand(Left, E),
|
|
|
|
elixir_dispatch:dispatch_require(Meta, ELeft, Right, Args, EL, fun(Receiver) ->
|
|
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL)
|
|
end);
|
|
|
|
%% Anonymous calls
|
|
|
|
expand({ { '.', DotMeta, [Expr] }, Meta, Args }, E) when is_list(Args) ->
|
|
{ EExpr, EE } = expand(Expr, E),
|
|
if
|
|
is_atom(EExpr) ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid function call :~ts.()", [EExpr]);
|
|
true ->
|
|
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EE)),
|
|
{ { { '.', DotMeta, [EExpr] }, Meta, EArgs }, elixir_env:mergev(EE, EA) }
|
|
end;
|
|
|
|
%% Invalid calls
|
|
|
|
expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
|
|
compile_error(Meta, E#elixir_env.file, "invalid call ~ts",
|
|
['Elixir.Macro':to_string(Invalid)]);
|
|
|
|
expand({ _, _, _ } = Tuple, E) ->
|
|
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
|
|
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
|
|
|
|
%% Literals
|
|
|
|
expand({ Left, Right }, E) ->
|
|
{ [ELeft, ERight], EE } = expand_args([Left, Right], E),
|
|
{ { ELeft, ERight }, EE };
|
|
|
|
expand(List, #elixir_env{context=match} = E) when is_list(List) ->
|
|
expand_list(List, fun expand/2, E, []);
|
|
|
|
expand(List, E) when is_list(List) ->
|
|
{ EArgs, { EC, EV } } = expand_list(List, fun expand_arg/2, {E, E}, []),
|
|
{ EArgs, elixir_env:mergea(EV, EC) };
|
|
|
|
expand(Function, E) when is_function(Function) ->
|
|
case (erlang:fun_info(Function, type) == { type, external }) andalso
|
|
(erlang:fun_info(Function, env) == { env, [] }) of
|
|
true ->
|
|
{ Function, E };
|
|
false ->
|
|
compile_error([{line,0}], E#elixir_env.file,
|
|
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Function)])
|
|
end;
|
|
|
|
expand(Other, E) when is_number(Other); is_atom(Other); is_binary(Other); is_pid(Other) ->
|
|
{ Other, E };
|
|
|
|
expand(Other, E) ->
|
|
compile_error([{line,0}], E#elixir_env.file,
|
|
"invalid quoted expression: ~ts", ['Elixir.Kernel':inspect(Other)]).
|
|
|
|
%% Helpers
|
|
|
|
expand_list([{ '|', Meta, [_, _] = Args }], Fun, Acc, List) ->
|
|
{ EArgs, EAcc } = lists:mapfoldl(Fun, Acc, Args),
|
|
expand_list([], Fun, EAcc, [{ '|', Meta, EArgs }|List]);
|
|
expand_list([H|T], Fun, Acc, List) ->
|
|
{ EArg, EAcc } = Fun(H, Acc),
|
|
expand_list(T, Fun, EAcc, [EArg|List]);
|
|
expand_list([], _Fun, Acc, List) ->
|
|
{ lists:reverse(List), Acc }.
|
|
|
|
expand_many(Args, E) ->
|
|
lists:mapfoldl(fun expand/2, E, Args).
|
|
|
|
%% Variables in arguments are not propagated from one
|
|
%% argument to the other. For instance:
|
|
%%
|
|
%% x = 1
|
|
%% foo(x = x + 2, x)
|
|
%% x
|
|
%%
|
|
%% Should be the same as:
|
|
%%
|
|
%% foo(3, 1)
|
|
%% 3
|
|
%%
|
|
%% However, lexical information is.
|
|
expand_arg(Arg, { Acc1, Acc2 }) ->
|
|
{ EArg, EAcc } = expand(Arg, Acc1),
|
|
{ EArg, { elixir_env:mergea(Acc1, EAcc), elixir_env:mergev(Acc2, EAcc) } }.
|
|
|
|
expand_args(Args, #elixir_env{context=match} = E) ->
|
|
lists:mapfoldl(fun expand/2, E, Args);
|
|
expand_args(Args, E) ->
|
|
{ EArgs, { EC, EV } } = lists:mapfoldl(fun expand_arg/2, {E, E}, Args),
|
|
{ EArgs, elixir_env:mergea(EV, EC) }.
|
|
|
|
%% Match/var helpers
|
|
|
|
var_kind(Meta, Kind) ->
|
|
case lists:keyfind(counter, 1, Meta) of
|
|
{ counter, Counter } -> Counter;
|
|
false -> Kind
|
|
end.
|
|
|
|
%% Locals
|
|
|
|
assert_no_ambiguous_op(Name, Meta, [Arg], E) ->
|
|
case lists:keyfind(ambiguous_op, 1, Meta) of
|
|
{ ambiguous_op, Kind } ->
|
|
case lists:member({ Name, Kind }, E#elixir_env.vars) of
|
|
true ->
|
|
compile_error(Meta, E#elixir_env.file, "\"~ts ~ts\" looks like a function call but "
|
|
"there is a variable named \"~ts\", please use explicit parenthesis or even spaces",
|
|
[Name, 'Elixir.Macro':to_string(Arg), Name]);
|
|
false ->
|
|
ok
|
|
end;
|
|
_ ->
|
|
ok
|
|
end;
|
|
assert_no_ambiguous_op(_Atom, _Meta, _Args, _E) ->
|
|
ok.
|
|
|
|
expand_local(Meta, Name, Args, #elixir_env{local=nil, function=nil} = E) ->
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { Name, Meta, EArgs }, EA };
|
|
expand_local(Meta, Name, Args, #elixir_env{local=nil, module=Module, function=Function} = E) ->
|
|
elixir_locals:record_local({ Name, length(Args) }, Module, Function),
|
|
{ EArgs, EA } = expand_args(Args, E),
|
|
{ { Name, Meta, EArgs }, EA };
|
|
expand_local(Meta, Name, Args, E) ->
|
|
expand({ { '.', Meta, [E#elixir_env.local, Name] }, Meta, Args }, E).
|
|
|
|
%% Remote
|
|
|
|
expand_remote(Receiver, DotMeta, Right, Meta, Args, E, EL) ->
|
|
if
|
|
is_atom(Receiver) -> elixir_lexical:record_remote(Receiver, E#elixir_env.lexical_tracker);
|
|
true -> ok
|
|
end,
|
|
{ EArgs, EA } = expand_args(Args, elixir_env:mergea(E, EL)),
|
|
{ { { '.', DotMeta, [Receiver, Right] }, Meta, EArgs }, elixir_env:mergev(EL, EA) }.
|
|
|
|
%% Lexical helpers
|
|
|
|
expand_opts(Meta, Kind, Allowed, Opts, E) ->
|
|
{ EOpts, EE } = expand(Opts, E),
|
|
validate_opts(Meta, Kind, Allowed, EOpts, EE),
|
|
{ EOpts, EE }.
|
|
|
|
validate_opts(Meta, Kind, Allowed, Opts, E) when is_list(Opts) ->
|
|
[begin
|
|
compile_error(Meta, E#elixir_env.file,
|
|
"unsupported option ~ts given to ~s", ['Elixir.Kernel':inspect(Key), Kind])
|
|
end || { Key, _ } <- Opts, not lists:member(Key, Allowed)];
|
|
|
|
validate_opts(Meta, Kind, _Allowed, _Opts, S) ->
|
|
compile_error(Meta, S#elixir_scope.file, "invalid options for ~s, expected a keyword list", [Kind]).
|
|
|
|
no_alias_opts(KV) when is_list(KV) ->
|
|
case lists:keyfind(as, 1, KV) of
|
|
{ as, As } -> lists:keystore(as, 1, KV, { as, no_alias_expansion(As) });
|
|
false -> KV
|
|
end;
|
|
no_alias_opts(KV) -> KV.
|
|
|
|
no_alias_expansion({ '__aliases__', Meta, [H|T] }) when (H /= 'Elixir') and is_atom(H) ->
|
|
{ '__aliases__', Meta, ['Elixir',H|T] };
|
|
no_alias_expansion(Other) ->
|
|
Other.
|
|
|
|
expand_require(Meta, Ref, KV, E) ->
|
|
RE = E#elixir_env{requires=ordsets:add_element(Ref, E#elixir_env.requires)},
|
|
expand_alias(Meta, false, Ref, KV, RE).
|
|
|
|
expand_alias(Meta, IncludeByDefault, Ref, KV, #elixir_env{context_modules=Context} = E) ->
|
|
New = expand_as(lists:keyfind(as, 1, KV), Meta, IncludeByDefault, Ref, E),
|
|
|
|
%% Add the alias to context_modules if defined is true.
|
|
%% This is used by defmodule in order to store the defined
|
|
%% module in context modules.
|
|
NewContext =
|
|
case lists:keyfind(defined, 1, Meta) of
|
|
{ defined, Mod } when is_atom(Mod) -> [Mod|Context];
|
|
false -> Context
|
|
end,
|
|
|
|
{ Aliases, MacroAliases } = elixir_aliases:store(Meta, New, Ref, KV, E#elixir_env.aliases,
|
|
E#elixir_env.macro_aliases, E#elixir_env.lexical_tracker),
|
|
|
|
E#elixir_env{aliases=Aliases, macro_aliases=MacroAliases, context_modules=NewContext}.
|
|
|
|
expand_as({ as, true }, _Meta, _IncludeByDefault, Ref, _E) ->
|
|
elixir_aliases:last(Ref);
|
|
expand_as({ as, false }, _Meta, _IncludeByDefault, Ref, _E) ->
|
|
Ref;
|
|
expand_as({ as, Atom }, Meta, _IncludeByDefault, _Ref, E) when is_atom(Atom) ->
|
|
case length(string:tokens(atom_to_list(Atom), ".")) of
|
|
1 -> compile_error(Meta, E#elixir_env.file,
|
|
"invalid value for keyword :as, expected an alias, got atom: ~ts", [elixir_aliases:inspect(Atom)]);
|
|
2 -> Atom;
|
|
_ -> compile_error(Meta, E#elixir_env.file,
|
|
"invalid value for keyword :as, expected an alias, got nested alias: ~ts", [elixir_aliases:inspect(Atom)])
|
|
end;
|
|
expand_as(false, _Meta, IncludeByDefault, Ref, _E) ->
|
|
if IncludeByDefault -> elixir_aliases:last(Ref);
|
|
true -> Ref
|
|
end;
|
|
expand_as({ as, Other }, Meta, _IncludeByDefault, _Ref, E) ->
|
|
compile_error(Meta, E#elixir_env.file,
|
|
"invalid value for keyword :as, expected an alias, got: ~ts", ['Elixir.Macro':to_string(Other)]).
|
|
|
|
%% Comprehensions
|
|
|
|
expand_comprehension(Meta, Kind, Args, E) ->
|
|
case elixir_utils:split_last(Args) of
|
|
{ Cases, [{do,Expr}] } ->
|
|
{ ECases, EC } = lists:mapfoldl(fun expand_comprehension_clause/2, E, Cases),
|
|
{ EExpr, EE } = expand(Expr, EC),
|
|
{ { Kind, Meta, ECases ++ [[{do,EExpr}]] }, elixir_env:mergec(E, EE) };
|
|
_ ->
|
|
compile_error(Meta, E#elixir_env.file, "missing do keyword in comprehension ~ts", [Kind])
|
|
end.
|
|
|
|
expand_comprehension_clause({Gen, Meta, [Left, Right]}, E) when Gen == inbits; Gen == inlist ->
|
|
{ ERight, ER } = expand(Right, E),
|
|
{ ELeft, EL } = elixir_exp_clauses:match(fun expand/2, Left, elixir_env:mergec(E, ER)),
|
|
{ { Gen, Meta, [ELeft, ERight] }, elixir_env:mergevc(EL, ER) };
|
|
expand_comprehension_clause(X, E) ->
|
|
expand(X, E).
|
|
|
|
%% Assertions
|
|
|
|
assert_no_match_or_guard_scope(Meta, Kind, E) ->
|
|
assert_no_match_scope(Meta, Kind, E),
|
|
assert_no_guard_scope(Meta, Kind, E).
|
|
assert_no_match_scope(Meta, _Kind, #elixir_env{context=match,file=File}) ->
|
|
compile_error(Meta, File, "invalid pattern in match clause");
|
|
assert_no_match_scope(_Meta, _Kind, _E) -> [].
|
|
assert_no_guard_scope(Meta, _Kind, #elixir_env{context=guard,file=File}) ->
|
|
compile_error(Meta, File, "invalid pattern in guard");
|
|
assert_no_guard_scope(_Meta, _Kind, _E) -> [].
|