Current section
Files
Jump to
Current section
Files
src/exat_pt.erl
-module(exat_pt).
-export([parse_transform/2, format_error/1]).
parse_transform(Forms0, _Options) ->
State = #{aliases => #{}, warnings => []},
{Forms, NewState} = ast_walk:forms(Forms0, fun walker/2, State),
report_problems(NewState),
remove_eof_and_ignore(Forms).
format_error(Error) -> atom_to_list(Error).
walker(State=#{aliases := CurAliases}, {attribute, Line, ex@alias, AliasMap})
when is_map(AliasMap)->
AliasPList = maps:to_list(AliasMap),
NewAliases = maps:from_list([{atom_to_ex_atom(From), atom_to_ex_atom(To)} ||
{From, To} <- AliasPList]),
% NOTE: nothing is being filtered now, we allow "bare" aliases
NewValidAliases = maps:filter(fun (nil, _) -> false;
(_, nil) -> false;
(_, _) -> true
end, NewAliases),
State = case {map_size(NewAliases), map_size(NewValidAliases)} of
{S, S} ->
State;
{_, _} ->
add_warning(State, ex_invalid_aliases,
#{all => NewAliases,
valid => NewValidAliases,
line => Line})
end,
FinalAliases = maps:merge(CurAliases, NewValidAliases),
NewState = State#{aliases := FinalAliases},
{ignore, NewState};
% similar to next until TODO of static compilation to map is solved
walker(State, Ast={call, L1, {remote, L2, {atom, L3, ex}, {atom, L4, FnName}},
[FieldsAst={map, _L5, _Fields}]}) ->
case atom_to_list(FnName) of
"s@" ++ RestName ->
{StructModAtomAst, State1} = rest_atom_to_ast(State, L3, RestName),
% TODO: if we can call 'Elixir.Struct.Id':__struct__()
% at compile time we can compile it diretly into a map with the
% '__struct__' key like elixir does (faster)
% 'Elixir.Struct.Id':__struct__(#{...})
NewAst = {call, L1,
{remote, L2, StructModAtomAst, {atom, L4, '__struct__'}},
[FieldsAst]},
{NewAst, State1};
_ ->
% TODO: warning?
{Ast, State}
end;
% here it's not an explicit map we call mod:__struct__/1
walker(State, Ast={call, L1, {remote, L2, {atom, L3, ex}, {atom, L4, FnName}},
Args}) ->
case atom_to_list(FnName) of
"s@" ++ RestName ->
{StructModAtomAst, State1} = rest_atom_to_ast(State, L3, RestName),
NewAst = {call, L1,
{remote, L2, StructModAtomAst, {atom, L4, '__struct__'}},
Args},
{NewAst, State1};
_ ->
% TODO: warning?
{Ast, State}
end;
% ex@Mod:fun(...) (or foo:bar(...) checking if foo is a bare alias)
walker(State=#{aliases := Aliases},
{call, L1, {remote, L2, {atom, AtomLine, ModName}, FnIdAst}, Args}) ->
case atom_to_list(ModName) of
"ex@" ++ RestName ->
{NewModNameAst, State1} = rest_atom_to_ast(State, AtomLine, RestName),
NewAst = {call, L1, {remote, L2, NewModNameAst, FnIdAst}, Args},
{NewAst, State1};
_ ->
MaybeNewModName = maps:get(ModName, Aliases, ModName),
NewAst = {call, L1,
{remote, L2, {atom, AtomLine, MaybeNewModName}, FnIdAst},
Args},
{NewAst, State}
end;
walker(State, Ast={tuple, L1, [{atom, _L2, AtomName}, {map, _L3, MapFields}]}) ->
case atom_to_list(AtomName) of
"ex@" ++ RestName ->
{StructAtomAst, State1} = rest_atom_to_ast(State, L1, RestName),
MatchStructAttrAst = {map_field_exact, L1,
{atom, L1, '__struct__'}, StructAtomAst},
NewAst = {map, L1, [MatchStructAttrAst | MapFields]},
{NewAst, State1};
_ ->
{Ast, State}
end;
walker(State, Other) ->
{Other, State}.
remove_eof_and_ignore(Ast) ->
lists:filter(fun ({eof, _}) -> false;
(ignore) -> false;
(_) -> true
end, Ast).
atom_to_ex_atom(Atom) ->
case atom_to_list(Atom) of
"ex@" ++ RestName ->
to_ex_atom(RestName);
_ -> Atom
end.
to_ex_atom(Name) ->
ElixirIdStr = "Elixir." ++ lists:flatten(string:replace(Name, "_", ".", all)),
list_to_atom(ElixirIdStr).
add_warning(State=#{warnings := Warns}, Type, Info) ->
State#{warnings := [{Type, Info}|Warns]}.
report_problems(#{warnings := []}) -> ok;
report_problems(#{warnings := Warns}) ->
[io:format(Warn) || Warn <- Warns].
rest_atom_to_ast(State, Line, RestName) ->
#{aliases := Aliases} = State,
ExAtomBase = to_ex_atom(RestName),
ExAtom = maps:get(ExAtomBase, Aliases, ExAtomBase),
{{atom, Line, ExAtom}, State}.