%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1998-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% Purpose : Transform Erlang compiler attributes -module(sys_pre_attributes). -export([parse_transform/2]). -define(OPTION_TAG, attributes). -record(state, {forms :: [form()], pre_ops = [] :: [op()], post_ops = [] :: [op()], options :: [option()]}). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Inserts, deletes and replaces Erlang compiler attributes. %% %% Valid options are: %% %% {attribute, insert, AttrName, NewAttrVal} %% {attribute, replace, AttrName, NewAttrVal} % replace first occurrence %% {attribute, delete, AttrName} %% %% The transformation is performed in two passes: %% %% pre_transform %% ------------- %% Searches for attributes in the list of Forms in order to %% delete or replace them. 'delete' will delete all occurrences %% of attributes with the given name. 'replace' will replace the %% first occurrence of the attribute. This pass is will only be %% performed if there are replace or delete operations stated %% as options. %% %% post_transform %% ------------- %% Looks up the module attribute and inserts the new attributes %% directly after. This pass will only be performed if there are %% any attributes left to be inserted after pre_transform. The left %% overs will be those replace operations that not has been performed %% due to that the pre_transform pass did not find the attribute plus %% all insert operations. -type attribute() :: atom(). -type value() :: term(). -type form() :: {function, integer(), atom(), arity(), _} | {attribute, integer(), attribute(), _}. -type option() :: compile:option() | {'attribute', 'insert', attribute(), value()} | {'attribute', 'replace', attribute(), value()} | {'attribute', 'delete', attribute()}. -type op() :: {'insert', attribute(), value()} | {'replace', attribute(), value()} | {'delete', attribute()}. -spec parse_transform([form()], [option()]) -> [form()]. parse_transform(Forms, Options) -> S = #state{forms = Forms, options = Options}, S2 = init_transform(Options, S), report_verbose("Pre options: ~p~n", [S2#state.pre_ops], S2), report_verbose("Post options: ~p~n", [S2#state.post_ops], S2), S3 = pre_transform(S2), S4 = post_transform(S3), S4#state.forms. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Computes the lists of pre_ops and post_ops that are %% used in the real transformation. init_transform([{attribute, insert, Name, Val} | Tail], S) -> Op = {insert, Name, Val}, PostOps = [Op | S#state.post_ops], init_transform(Tail, S#state{post_ops = PostOps}); init_transform([{attribute, replace, Name, Val} | Tail], S) -> Op = {replace, Name, Val}, PreOps = [Op | S#state.pre_ops], PostOps = [Op | S#state.post_ops], init_transform(Tail, S#state{pre_ops = PreOps, post_ops = PostOps}); init_transform([{attribute, delete, Name} | Tail], S) -> Op = {delete, Name}, PreOps = [Op | S#state.pre_ops], init_transform(Tail, S#state{pre_ops = PreOps}); init_transform([_ | T], S) -> init_transform(T, S); init_transform([], S) -> S. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Handle delete and perhaps replace pre_transform(#state{pre_ops = []} = S) -> S; pre_transform(S) -> pre_transform(S#state.forms, [], S). pre_transform([H | T], Acc, S) -> case H of {attribute, Line, Name, Val} -> case lists:keyfind(Name, 2, S#state.pre_ops) of false -> pre_transform(T, [H | Acc], S); {replace, Name, NewVal} -> report_warning("Replace attribute ~p: ~p -> ~p~n", [Name, Val, NewVal], S), New = {attribute, Line, Name, NewVal}, Pre = lists:keydelete(Name, 2, S#state.pre_ops), Post = lists:keydelete(Name, 2, S#state.post_ops), S2 = S#state{pre_ops = Pre, post_ops = Post}, if Pre == [] -> %% No need to search the rest of the Forms Forms = lists:reverse(Acc, [New | T]), S2#state{forms = Forms}; true -> pre_transform(T, [New | Acc], S2) end; {delete, Name} -> report_warning("Delete attribute ~p: ~p~n", [Name, Val], S), pre_transform(T, Acc, S) end; _Any -> pre_transform(T, [H | Acc], S) end; pre_transform([], Acc, S) -> S#state{forms = lists:reverse(Acc)}. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Handle insert and perhaps replace post_transform(#state{post_ops = []} = S) -> S; post_transform(S) -> post_transform(S#state.forms, [], S). post_transform([H | T], Acc, S) -> case H of {attribute, Line, module, _Val} = Attribute -> Acc2 = lists:reverse([Attribute | Acc]), Forms = Acc2 ++ attrs(S#state.post_ops, Line, S) ++ T, S#state{forms = Forms, post_ops = []}; _Any -> post_transform(T, [H | Acc], S) end; post_transform([], Acc, S) -> S#state{forms = lists:reverse(Acc)}. attrs([{replace, Name, NewVal} | T], Line, S) -> report_verbose("Insert attribute ~p: ~p~n", [Name, NewVal], S), [{attribute, Line, Name, NewVal} | attrs(T, Line, S)]; attrs([{insert, Name, NewVal} | T], Line, S) -> report_verbose("Insert attribute ~p: ~p~n", [Name, NewVal], S), [{attribute, Line, Name, NewVal} | attrs(T, Line, S)]; attrs([], _, _) -> []. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Report functions. %% %% Warning messages are controlled with the 'report_warnings' compiler option %% Verbose messages are controlled with the 'verbose' compiler option report_warning(Format, Args, S) -> case is_warning(S) of true -> io:format("~p: * WARNING * " ++ Format, [?MODULE | Args]); false -> ok end. report_verbose(Format, Args, S) -> case is_verbose(S) of true -> io:format("~p: " ++ Format, [?MODULE | Args]); false -> ok end. is_warning(S) -> lists:member(report_warnings, S#state.options) or is_verbose(S). is_verbose(S) -> lists:member(verbose, S#state.options).