Current section
Files
Jump to
Current section
Files
src/decorators.erl
-module(decorators).
-include_lib("eunit/include/eunit.hrl").
-export([parse_transform/2, pretty_print/1]).
%% TODO: add warnings for rogue decorators
parse_transform(Ast, _Options) ->
%%io:format("~p~n=======~n", [Ast]),
%%io:format("~s~n=======~n", [pretty_print(Ast)]),
{ExtendedAst2, RogueDecorators} = lists:mapfoldl(fun transform_node/2, [], Ast),
Ast2 = lists:flatten(lists:filter(fun (Node) -> Node =/= nil end, ExtendedAst2))
++ emit_errors_for_rogue_decorators(RogueDecorators),
%%io:format("~p~n<<<<~n", [Ast2]),
%%io:format("~s~n>>>>~n", [pretty_print(Ast2)]),
Ast2.
pretty_print(Ast) -> lists:flatten([erl_pp:form(N) || N<-Ast]).
emit_errors_for_rogue_decorators(DecoratorList) ->
[{error, {Line, erl_parse, ["rogue decorator ", io_lib:format("~p", [D])]}} || {attribute, Line, decorate, D} <- DecoratorList].
%% transforms module level nodes
%% see http://www.erlang.org/doc/apps/erts/absform.html
%% outputs nil (to swallow the node), a single node, or a list of nodes.
%% nil nodes are removed in a subsequent pass and the lists flattened
transform_node(Node={attribute, _Line, decorate, _Decorator}, DecoratorList) ->
%% keep a list of decorators but dont emit them in the code.
%% this is important as you arent meant to have attributes after functions in a module
{nil, [Node|DecoratorList]};
transform_node(Node={function, _Line, _FuncName, _Arity, _Clauses}, []) ->
%% pass through decoratorless functions
{Node, []};
transform_node(Node={function, _Line, _FuncName, _Arity, _Clauses}, DecoratorList) ->
%% apply decorators to this function and reset decorator list
{apply_decorators(Node, DecoratorList), []};
transform_node(Node={eof, _Line}, DecoratorList) ->
{[Node| emit_errors_for_rogue_decorators(DecoratorList)], []};
transform_node(Node, DecoratorList) ->
%% some other form (only other valid forms are other attributes)
%% keep going
{Node, DecoratorList}.
apply_decorators(Node={function, Line, FuncName, Arity, _Clauses}, DecoratorList) when length(DecoratorList) > 0 ->
[
%% output the original function renamed
function_form_original(Node),
%% output a trampoline into our decorator chain
function_form_trampoline(Line, FuncName, Arity, DecoratorList)
%% output our decorator chain
| function_forms_decorator_chain(Line, FuncName, Arity, DecoratorList)
].
function_form_original({function, Line, FuncName, Arity, Clauses}) ->
{function, Line, generated_func_name({original, FuncName}), Arity, Clauses}.
%% outputs a single clause function that gets the first decorator chain function and calls it
function_form_trampoline(Line, FuncName, Arity, DecoratorList) ->
NumDecorators = length(DecoratorList),
ArgNames = arg_names(Arity),
{function, Line, FuncName, Arity,
[{clause, Line,
emit_arguments(Line, ArgNames),
emit_guards(Line, []),
[emit_local_call(
Line,
generated_func_name({decorator_wrapper, FuncName, NumDecorators}),
emit_arguments(Line, ArgNames))]
}]}.
function_forms_decorator_chain(Line, FuncName, Arity, DecoratorList) ->
NumDecorators = length(DecoratorList),
DecoratorIndexes = lists:zip(DecoratorList, lists:seq(1, NumDecorators)),
[function_form_decorator_chain(Line, FuncName, Arity, D, I)
|| {{attribute, _, decorate, D}, I} <- DecoratorIndexes].
function_form_decorator_chain(Line, FuncName, Arity, Decorator, DecoratorIndex) ->
ArgNames = arg_names(Arity),
NextFuncName = case DecoratorIndex - 1 of
0 ->
generated_func_name({original, FuncName});
N ->
generated_func_name({decorator_wrapper, FuncName, N})
end,
{function, Line,
generated_func_name({decorator_wrapper, FuncName, DecoratorIndex}),
Arity,
[{clause, Line,
emit_arguments(Line, ArgNames),
emit_guards(Line, []),
[
%% DecMod:Decfun(fun NextFun/1, [Arg1, Arg2, ...]).
emit_decorated_fun(Line, Decorator, NextFuncName, ArgNames)
]
}]
}.
emit_decorated_fun(Line, {DecMod, DecFun}, InnerFunName, ArgNames)
when is_atom(DecMod), is_atom(DecFun) ->
emit_decorated_fun(Line, {DecMod, DecFun, []}, InnerFunName, ArgNames);
emit_decorated_fun(Line, DecFun, InnerFunName, ArgNames)
when is_atom(DecFun) ->
emit_decorated_fun(Line, {DecFun, []}, InnerFunName, ArgNames);
emit_decorated_fun(Line, {DecMod, DecFun, DecData}, InnerFunName, ArgNames)
when is_list(DecData) ->
Arity = length(ArgNames),
{call, Line,
{remote, Line, {atom, Line, DecMod}, {atom, Line, DecFun}},
[
{'fun', Line, {function, InnerFunName, Arity}},
emit_var_list(Line, ArgNames),
erl_parse:abstract(DecData)
]
};
emit_decorated_fun(Line, {DecFun, DecData}, InnerFunName, ArgNames)
when is_list(DecData) ->
Arity = length(ArgNames),
ArgList = [{'fun', Line, {function, InnerFunName, Arity}},
emit_var_list(Line, ArgNames),
erl_parse:abstract(DecData)],
emit_local_call(Line, DecFun, ArgList).
emit_local_call(Line, FuncName, ArgList) ->
{call, Line, {atom, Line, FuncName}, ArgList}.
emit_arguments(Line, AtomList) ->
[{var, Line, Arg} || Arg <- AtomList].
emit_guards(_Line, []) ->
[];
emit_guards(_, _) ->
throw(not_yet_implemented).
emit_var_list(Line, AtomList) ->
%% build a list of args out of cons cells
%% {cons, 43, {var, 43, 'Arg1'}, {cons, 43, {var, 43, 'Arg2'}, {nil, 43}}}
lists:foldr(fun (Arg, Acc) ->
{cons, Line, {var, Line, Arg}, Acc}
end, {nil, Line}, AtomList).
generated_func_name({original, OrigName} ) ->
atom_name([OrigName, "_original___"]);
generated_func_name({trampoline, OrigName} ) ->
OrigName;
generated_func_name({decorator_wrapper, OrigName, N} ) ->
atom_name([OrigName, "_decorator", N, "___"]).
%% list() -> atom()
atom_name(Elements) ->
list_to_atom(
lists:flatten(
lists:map(
fun (A) when is_atom(A) -> atom_to_list(A);
(A) when is_number(A) -> io_lib:format("~p", [A]);
(A) when is_binary(A) -> io_lib:format("~s", [A]);
(A) when is_list(A) -> io_lib:format("~s", [A])
end,
Elements
))).
arg_names(Arity) ->
[atom_name(["Arg", ArgNum]) || ArgNum <- lists:seq(1, Arity)].
% for example
% -decorate({decmod, decfun2}).
% -decorate({decmod, decfun1}).
% baz(N1, N2) -> 0.
% is transformed into
% baz_arity2_original(N1, N2) -> 0.
% baz_arity2_0([N1, N2]) -> baz_arity2_original(N1, N2).
% baz_arity2_1(Args) ->
% F = decmod:decfun1(fun baz_arity2_0/1, Args),
% F().
% baz_arity2_2(Args) ->
% F = decmod:decfun2(fun baz_arity2_1/1, Args),
% F().
% baz(N1, N2) -> baz_arity2_2([N1, N2]).
% which is output as
% {function, 35, baz_arity2_original, 0, [{clause, 35, [], [], [{integer, 35, 0}]}]},
% {function, 36, baz_arity2_0, 1,
% [{clause, 36,
% [{cons, 36, {var, 36, 'N1'}, {cons, 36, {var, 36, 'N2'}, {nil, 36}}}],
% [],
% [{call, 36,
% {atom, 36, baz_arity2_original},
% [{var, 36, 'N1'}, {var, 36, 'N2'}]}]}]},
% {function, 37, baz_arity2_1, 1,
% [{clause, 37,
% [{var, 37, 'Args'}],
% [],
% [{match, 38,
% {var, 38, 'F'},
% {call, 38,
% {remote, 38, {atom, 38, decmod}, {atom, 38, decfun1}},
% [{'fun', 38, {function, baz_arity2_0, 1}}, {var, 38, 'Args'}]}},
% {call, 39, {var, 39, 'F'}, []}]}]},
% {function, 40, baz_arity2_2, 1,
% [{clause, 40,
% [{var, 40, 'Args'}],
% [],
% [{match, 41,
% {var, 41, 'F'},
% {call, 41,
% {remote, 41, {atom, 41, decmod}, {atom, 41, decfun2}},
% [{'fun', 41, {function, baz_arity2_1, 1}}, {var, 41, 'Args'}]}},
% {call, 42, {var, 42, 'F'}, []}]}]},
% {function, 43, baz, 2,
% [{clause, 43,
% [{var, 43, 'N1'}, {var, 43, 'N2'}],
% [],
% [{call, 43,
% {atom, 43, baz_arity2_2},
% [{cons, 43, {var, 43, 'N1'}, {cons, 43, {var, 43, 'N2'}, {nil, 43}}}]}]}]},
atom_name_test_() ->
[
?_assertEqual(foobar, atom_name([foo, bar])),
?_assertEqual(foobarbaz1, atom_name([foo, "bar", <<"baz">>, 1]))
].
args_to_list_form_of_args_test() ->
Line=1,
?assertEqual(
{cons, Line, {var, Line, 'Arg1'}, {cons, Line, {var, Line, 'Arg2'}, {nil, Line}}},
emit_var_list(Line, ['Arg1', 'Arg2'])
).