122 lines
4.3 KiB
Erlang
122 lines
4.3 KiB
Erlang
-module(elixir_map).
|
|
-export([expand_map/3, translate_map/3, expand_struct/4, translate_struct/4]).
|
|
-import(elixir_errors, [compile_error/4]).
|
|
-include("elixir.hrl").
|
|
|
|
expand_map(Meta, [{ '|', UpdateMeta, [Left, Right]}], E) ->
|
|
{ [ELeft|ERight], EA } = elixir_exp:expand_args([Left|Right], E),
|
|
{ { '%{}', Meta, [{ '|', UpdateMeta, [ELeft, ERight] }] }, EA };
|
|
expand_map(Meta, Args, E) ->
|
|
{ EArgs, EA } = elixir_exp:expand_args(Args, E),
|
|
{ { '%{}', Meta, EArgs }, EA }.
|
|
|
|
expand_struct(Meta, Left, Right, E) ->
|
|
{ [ELeft, ERight], EE } = elixir_exp:expand_args([Left, Right], E),
|
|
|
|
case is_atom(ELeft) of
|
|
true -> ok;
|
|
false ->
|
|
compile_error(Meta, E#elixir_env.file, "expected struct name to be a compile "
|
|
"time atom or alias, got: ~ts", ['Elixir.Macro':to_string(ELeft)])
|
|
end,
|
|
|
|
case E#elixir_env.module of
|
|
ELeft -> ok;
|
|
_ -> elixir_aliases:ensure_loaded(Meta, ELeft, E)
|
|
end,
|
|
|
|
case ERight of
|
|
{ '%{}', _, _ } -> ok;
|
|
_ -> compile_error(Meta, E#elixir_env.file,
|
|
"expected struct to be followed by a map, got: ~ts",
|
|
['Elixir.Macro':to_string(ERight)])
|
|
end,
|
|
|
|
{ { '%', Meta, [ELeft, ERight] }, EE }.
|
|
|
|
translate_map(Meta, Args, S) ->
|
|
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
|
|
translate_map(Meta, Assocs, TUpdate, US).
|
|
|
|
translate_struct(Meta, Name, { '%{}', MapMeta, Args }, S) ->
|
|
{ Assocs, TUpdate, US } = extract_assoc_update(Args, S),
|
|
|
|
Struct = case S#elixir_scope.module of
|
|
Name ->
|
|
elixir_locals:record_local({ '__struct__', 0 }, S#elixir_scope.module, S#elixir_scope.function),
|
|
(elixir_locals:local_for(Name, '__struct__', 0, def))();
|
|
_ ->
|
|
Name:'__struct__'()
|
|
end,
|
|
|
|
case is_map(Struct) of
|
|
true ->
|
|
assert_struct_keys(Meta, Name, Struct, Assocs, S);
|
|
false ->
|
|
compile_error(Meta, S#elixir_scope.file, "expected ~ts.__struct__/0 to "
|
|
"return a map, got: ~ts", [elixir_aliases:inspect(Name), 'Elixir.Kernel':inspect(Struct)])
|
|
end,
|
|
|
|
case TUpdate of
|
|
nil ->
|
|
CreateAssocs = maps:put('__struct__', Name,
|
|
maps:merge(Struct, maps:from_list(Assocs))),
|
|
translate_map(MapMeta, maps:to_list(CreateAssocs), nil, US);
|
|
_ ->
|
|
Line = ?line(Meta),
|
|
{ VarName, _, VS } = elixir_scope:build_var('_', US),
|
|
|
|
Var = { var, Line, VarName },
|
|
Map = { map, Line, [{ map_field_exact, Line, { atom, Line, '__struct__' }, { atom, Line, Name }}] },
|
|
|
|
Match = { match, Line, Var, Map },
|
|
Error = { tuple, Line, [{ atom, Line, badstruct }, { atom, Line, Name }, Var] },
|
|
|
|
{ TMap, TS } = translate_map(MapMeta, Assocs, Var, VS),
|
|
|
|
{ { 'case', Line, TUpdate, [
|
|
{ clause, Line, [Match], [], [TMap] },
|
|
{ clause, Line, [Var], [], [?wrap_call(Line, erlang, error, [Error])] }
|
|
] }, TS }
|
|
end.
|
|
|
|
%% Helpers
|
|
|
|
translate_map(Meta, Assocs, TUpdate, #elixir_scope{extra=Extra} = S) ->
|
|
{ Op, KeyFun, ValFun } = extract_key_val_op(TUpdate, S),
|
|
|
|
Line = ?line(Meta),
|
|
|
|
{ TArgs, SA } = lists:mapfoldl(fun({ Key, Value }, Acc) ->
|
|
{ TKey, Acc1 } = KeyFun(Key, Acc),
|
|
{ TValue, Acc2 } = ValFun(Value, Acc1#elixir_scope{extra=Extra}),
|
|
{ { Op, ?line(Meta), TKey, TValue }, Acc2 }
|
|
end, S, Assocs),
|
|
|
|
build_map(Line, TUpdate, TArgs, SA).
|
|
|
|
extract_assoc_update([{ '|', _Meta, [Update, Args] }], S) ->
|
|
{ TArg, SA } = elixir_translator:translate_arg(Update, S, S),
|
|
{ Args, TArg, SA };
|
|
extract_assoc_update(Args, SA) -> { Args, nil, SA }.
|
|
|
|
extract_key_val_op(_TUpdate, #elixir_scope{context=match}) ->
|
|
{ map_field_exact,
|
|
fun(X, Acc) -> elixir_translator:translate(X, Acc#elixir_scope{extra=map_key}) end,
|
|
fun elixir_translator:translate/2 };
|
|
extract_key_val_op(TUpdate, S) ->
|
|
KS = #elixir_scope{extra=map_key},
|
|
Op = if TUpdate == nil -> map_field_assoc; true -> map_field_exact end,
|
|
{ Op,
|
|
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, KS) end,
|
|
fun(X, Acc) -> elixir_translator:translate_arg(X, Acc, S) end }.
|
|
|
|
build_map(Line, nil, TArgs, SA) -> { { map, Line, TArgs }, SA };
|
|
build_map(Line, TUpdate, TArgs, SA) -> { { map, Line, TUpdate, TArgs }, SA }.
|
|
|
|
assert_struct_keys(Meta, Name, Struct, Assocs, S) ->
|
|
[begin
|
|
compile_error(Meta, S#elixir_scope.file, "unknown key ~ts for struct ~ts",
|
|
['Elixir.Kernel':inspect(Key), elixir_aliases:inspect(Name)])
|
|
end || { Key, _ } <- Assocs, not maps:is_key(Key, Struct)].
|