%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% --------------------------------------------------
%% This file is provided to you 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.
%% --------------------------------------------------
%% File : parse_trans.erl
%% @author : Ulf Wiger <ulf@wiger.net>
%% @end
%% Description :
%%
%% Created : 13 Feb 2006 by Ulf Wiger <ulf@wiger.net> (then Ericsson)
%%%-------------------------------------------------------------------
%% @doc Generic parse transform library for Erlang.
%%
%% <p>...</p>
%%
%% @end
-module(parse_trans).
-compile({no_auto_import,[error/3]}).
-export([plain_transform/2]).
-export([
inspect/4,
transform/4,
depth_first/4,
revert/1,
revert_form/1,
format_exception/2, format_exception/3,
return/2
]).
-export([
error/3,
format_error/1
]).
-export([
initial_context/2,
do_inspect/4,
do_transform/4,
do_depth_first/4,
top/3
]).
-export([do_insert_forms/4,
replace_function/4,
replace_function/5,
export_function/3]).
-export([
context/2,
get_pos/1,
get_file/1,
get_module/1,
get_attribute/2,
get_attribute/3,
get_orig_syntax_tree/1,
function_exists/3,
optionally_pretty_print/3,
pp_src/2,
pp_beam/1, pp_beam/2
]).
-import(erl_syntax, [atom_value/1,
attribute_name/1,
attribute_arguments/1,
string_value/1,
type/1
]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-record(context, {module,
function,
arity,
file,
options}).
%% Useful macros for debugging and error reporting
-define(HERE, {?MODULE, ?LINE}).
-define(DUMMY_LINE, 9999).
-define(ERROR(R, F, I, Trace),
begin
rpt_error(R, F, I, Trace),
throw({error,get_pos(I),{R, Trace}})
end).
-export_type([forms/0]).
%% Typedefs
-type form() :: any().
-type forms() :: [form()].
-type options() :: [{atom(), any()}].
-type type() :: atom().
-type xform_f_rec() :: fun((type(), form(), #context{}, Acc) ->
{form(), boolean(), Acc}
| {forms(), form(), forms(), boolean(), Acc}).
-type xform_f_df() :: fun((type(), form(), #context{}, Acc) ->
{form(), Acc}
| {forms(), form(), forms(), Acc}).
-type insp_f() :: fun((type(), form(), #context{}, A) -> {boolean(), A}).
%%% @spec (Reason, Form, Info) -> throw()
%%% Info = [{Key,Value}]
%%%
%%% @doc
%%% <p>Used to report errors detected during the parse transform.</p>
%%% @end
%%%
-spec error(string(), any(), [{any(),any()}]) ->
none().
error(R, _F, I) ->
ST = erlang:process_info(self(), current_stacktrace),
% rpt_error(R, F, I, ST),
throw({error,get_pos(I),{R, ST}}).
%% @spec plain_transform(Fun, Forms) -> forms()
%% Fun = function()
%% Forms = forms()
%%
%% @doc
%% Performs a transform of `Forms' using the fun `Fun(Form)'. `Form' is always
%% an Erlang abstract form, i.e. it is not converted to syntax_tools
%% representation. The intention of this transform is for the fun to have a
%% catch-all clause returning `continue'. This will ensure that it stays robust
%% against additions to the language.
%%
%% `Fun(Form)' must return either of the following:
%%
%% * `NewForm' - any valid form
%% * `continue' - dig into the sub-expressions of the form
%% * `{done, NewForm}' - Replace `Form' with `NewForm'; return all following
%% forms unchanged
%% * `{error, Reason}' - Abort transformation with an error message.
%%
%% Example - This transform fun would convert all instances of `P ! Msg' to
%% `gproc:send(P, Msg)':
%% <pre>
%% parse_transform(Forms, _Options) ->
%% parse_trans:plain_transform(fun do_transform/1, Forms).
%%
%% do_transform({'op', L, '!', Lhs, Rhs}) ->
%% [NewLhs] = parse_trans:plain_transform(fun do_transform/1, [Lhs]),
%% [NewRhs] = parse_trans:plain_transform(fun do_transform/1, [Rhs]),
%% {call, L, {remote, L, {atom, L, gproc}, {atom, L, send}},
%% [NewLhs, NewRhs]};
%% do_transform(_) ->
%% continue.
%% </pre>
%% @end
%%
plain_transform(Fun, Forms) when is_function(Fun, 1), is_list(Forms) ->
plain_transform1(Fun, Forms).
plain_transform1(_, []) ->
[];
plain_transform1(Fun, [F|Fs]) when is_atom(element(1,F)) ->
case Fun(F) of
skip ->
plain_transform1(Fun, Fs);
continue ->
[list_to_tuple(plain_transform1(Fun, tuple_to_list(F))) |
plain_transform1(Fun, Fs)];
{done, NewF} ->
[NewF | Fs];
{error, Reason} ->
erlang:error(Reason, F, [{form, F}]);
NewF when is_tuple(NewF) ->
[NewF | plain_transform1(Fun, Fs)]
end;
plain_transform1(Fun, [L|Fs]) when is_list(L) ->
[plain_transform1(Fun, L) | plain_transform1(Fun, Fs)];
plain_transform1(Fun, [F|Fs]) ->
[F | plain_transform1(Fun, Fs)];
plain_transform1(_, F) ->
F.
%% @spec (list()) -> integer()
%%
%% @doc
%% Tries to retrieve the line number from an erl_syntax form. Returns a
%% (very high) dummy number if not successful.
%% @end
%%
-spec get_pos(list()) ->
erl_anno:location().
get_pos(I) when is_list(I) ->
case proplists:get_value(form, I) of
undefined ->
?DUMMY_LINE;
Form ->
Anno = erl_syntax:get_pos(Form),
erl_anno:location(Anno)
end.
%%% @spec (Forms) -> string()
%%% @doc
%%% Returns the name of the file being compiled.
%%% @end
%%%
-spec get_file(forms()) ->
string().
get_file(Forms) ->
string_value(hd(get_attribute(file, Forms, [erl_syntax:string("undefined")]))).
%%% @spec (Forms) -> atom()
%%% @doc
%%% Returns the name of the module being compiled.
%%% @end
%%%
-spec get_module([any()]) ->
atom().
get_module(Forms) ->
atom_value(hd(get_attribute(module, Forms, [erl_syntax:atom(undefined)]))).
%%% @spec (A, Forms) -> any()
%%% A = atom()
%%%
%%% @doc
%%% Returns the value of the first occurrence of attribute A.
%%% @end
%%%
-spec get_attribute(atom(), [any()]) ->
'none' | [erl_syntax:syntaxTree()].
%%
get_attribute(A, Forms) -> get_attribute(A,Forms,[erl_syntax:atom(undefined)]).
get_attribute(A, Forms, Undef) ->
case find_attribute(A, Forms) of
false ->
Undef;
Other ->
Other
end.
find_attribute(A, [F|Forms]) ->
case type(F) == attribute
andalso atom_value(attribute_name(F)) == A of
true ->
attribute_arguments(F);
false ->
find_attribute(A, Forms)
end;
find_attribute(_, []) ->
false.
%% @spec (Fname::atom(), Arity::integer(), Forms) -> boolean()
%%
%% @doc
%% Checks whether the given function is defined in Forms.
%% @end
%%
-spec function_exists(atom(), integer(), forms()) ->
boolean().
function_exists(Fname, Arity, Forms) ->
Fns = proplists:get_value(
functions, erl_syntax_lib:analyze_forms(Forms), []),
lists:member({Fname,Arity}, Fns).
%%% @spec (Forms, Options) -> #context{}
%%%
%%% @doc
%%% Initializes a context record. When traversing through the form
%%% list, the context is updated to reflect the current function and
%%% arity. Static elements in the context are the file name, the module
%%% name and the options passed to the transform function.
%%% @end
%%%
-spec initial_context(forms(), options()) ->
#context{}.
initial_context(Forms, Options) ->
File = get_file(Forms),
Module = get_module(Forms),
#context{file = File,
module = Module,
options = Options}.
%%% @spec (Fun, Acc, Forms, Options) -> {TransformedForms, NewAcc}
%%% Fun = function()
%%% Options = [{Key,Value}]
%%%
%%% @doc
%%% Makes one pass
%%% @end
-spec transform(xform_f_rec(), Acc, forms(), options()) ->
{forms(), Acc} | {error, list()}.
transform(Fun, Acc, Forms, Options) when is_function(Fun, 4) ->
do(fun do_transform/4, Fun, Acc, Forms, Options).
-spec depth_first(xform_f_df(), Acc, forms(), options()) ->
{forms(), Acc} | {error, list()}.
depth_first(Fun, Acc, Forms, Options) when is_function(Fun, 4) ->
do(fun do_depth_first/4, Fun, Acc, Forms, Options).
do(Transform, Fun, Acc, Forms, Options) ->
Context = initial_context(Forms, Options),
File = Context#context.file,
try Transform(Fun, Acc, Forms, Context) of
{NewForms, Acc1} when is_list(NewForms) ->
NewForms1 = optionally_renumber(NewForms, Options),
optionally_pretty_print(NewForms1, Options, Context),
{NewForms1, Acc1}
catch
error:Reason:ST ->
{error,
[{File, [{?DUMMY_LINE, ?MODULE,
{Reason, ST}}]}]};
throw:{error, Ln, What} ->
{error, [{error, {Ln, ?MODULE, What}}]}
end.
-spec top(function(), forms(), list()) ->
forms() | {error, term()}.
top(F, Forms, Options) ->
Context = initial_context(Forms, Options),
File = Context#context.file,
try F(Forms, Context) of
{error, Reason} -> {error, Reason};
NewForms when is_list(NewForms) ->
NewForms1 = optionally_renumber(NewForms, Options),
optionally_pretty_print(NewForms1, Options, Context),
NewForms1
catch
error:Reason:ST ->
{error,
[{File, [{?DUMMY_LINE, ?MODULE,
{Reason, ST}}]}]};
throw:{error, Ln, What} ->
{error, [{File, [{Ln, ?MODULE, What}]}], []}
end.
replace_function(F, Arity, NewForm, Forms) ->
replace_function(F, Arity, NewForm, Forms, []).
replace_function(F, Arity, NewForm, Forms, Opts) ->
{NewForms, _} =
do_transform(
fun(function, Form, _Ctxt, Acc) ->
case erl_syntax:revert(Form) of
{function, _, F, Arity, _} = RevForm ->
{[], NewForm, with_original_f(RevForm, Opts),
false, Acc};
_ ->
{Form, false, Acc}
end;
(_, Form, _Ctxt, Acc) ->
{Form, false, Acc}
end, false, Forms, initial_context(Forms, [])),
revert(maybe_export_renamed(NewForms, Arity, Opts)).
with_original_f({function,_,_,_,_} = Form, Opts) ->
case lists:keyfind(rename_original, 1, Opts) of
{_, NewName} when is_atom(NewName) ->
[setelement(3, Form, NewName)];
_ ->
[]
end.
maybe_export_renamed(Forms, Arity, Opts) ->
case lists:keyfind(rename_original, 1, Opts) of
{_, NewName} when is_atom(NewName) ->
export_function(NewName, Arity, Forms);
_ ->
Forms
end.
export_function(F, Arity, Forms) ->
do_insert_forms(above, [{attribute, 1, export, [{F, Arity}]}], Forms,
initial_context(Forms, [])).
-spec do_insert_forms(above | below, forms(), forms(), #context{}) ->
forms().
do_insert_forms(above, Insert, Forms, Context) when is_list(Insert) ->
{NewForms, _} =
do_transform(
fun(function, F, _Ctxt, false) ->
{Insert, F, [], _Recurse = false, true};
(_, F, _Ctxt, Acc) ->
{F, _Recurse = false, Acc}
end, false, Forms, Context),
NewForms;
do_insert_forms(below, Insert, Forms, _Context) when is_list(Insert) ->
insert_below(Forms, Insert).
insert_below([F|Rest], Insert) ->
case type(F) of
eof_marker ->
%% In the unlikely case someone misused eof_marker
Insert ++ [F | Rest];
_ ->
[F|insert_below(Rest, Insert)]
end.
-spec optionally_pretty_print(forms(), options(), #context{}) ->
ok.
optionally_pretty_print(Result, Options, Context) ->
DoPP = option_value(pt_pp_src, Options, Result),
DoLFs = option_value(pt_log_forms, Options, Result),
File = Context#context.file,
if DoLFs ->
Out1 = outfile(File, forms),
{ok,Fd} = file:open(Out1, [write]),
try lists:foreach(fun(F) -> io:fwrite(Fd, "~p.~n", [F]) end, Result)
after
ok = file:close(Fd)
end;
true -> ok
end,
if DoPP ->
Out2 = outfile(File, pp),
pp_src(Result, Out2),
io:fwrite("Pretty-printed in ~p~n", [Out2]);
true -> ok
end.
optionally_renumber(Result, Options) ->
case option_value(pt_renumber, Options, Result) of
true ->
io:fwrite("renumbering...~n", []),
Rev = revert(Result),
renumber_(Rev);
false ->
Result
end.
renumber_(L) when is_list(L) ->
{Result, _} = renumber_(L, 1),
Result.
renumber_(L, Acc) when is_list(L) ->
lists:mapfoldl(fun renumber_/2, Acc, L);
renumber_(T, Prev) when is_tuple(T) ->
case is_form(T) of
true ->
New = Prev+1,
NewE2 = update_line(element(2, T), New),
T1 = setelement(2, T, NewE2),
{Res, NewAcc} = renumber_(tuple_to_list(T1), New),
{list_to_tuple(Res), NewAcc};
false ->
L = tuple_to_list(T),
{Res, NewAcc} = renumber_(L, Prev),
{list_to_tuple(Res), NewAcc}
end;
renumber_(X, Prev) ->
{X, Prev}.
is_form(T) when element(1,T)==type -> true;
is_form(T) ->
try erl_syntax:type(T),
true
catch
error:_ ->
false
end.
update_line(Element2, Line) ->
case erl_anno:is_anno(Element2) of
true ->
erl_anno:set_line(Line, Element2);
false -> % location
A = erl_anno:new(Element2),
NewA = erl_anno:set_line(Line, A),
erl_anno:location(NewA)
end.
option_value(Key, Options, Result) ->
case proplists:get_value(Key, Options) of
undefined ->
case find_attribute(Key,Result) of
[Expr] ->
type(Expr) == atom andalso
atom_value(Expr) == true;
_ ->
false
end;
V when is_boolean(V) ->
V
end.
%%% @spec (Fun, Forms, Acc, Options) -> NewAcc
%%% Fun = function()
%%% @doc
%%% Equivalent to do_inspect(Fun,Acc,Forms,initial_context(Forms,Options)).
%%% @end
%%%
-spec inspect(insp_f(), A, forms(), options()) ->
A.
inspect(F, Acc, Forms, Options) ->
Context = initial_context(Forms, Options),
do_inspect(F, Acc, Forms, Context).
outfile(File, Type) ->
"lre." ++ RevF = lists:reverse(File),
lists:reverse(RevF) ++ ext(Type).
ext(pp) -> ".xfm";
ext(forms) -> ".xforms".
%% @spec (Forms, Out::filename()) -> ok
%%
%% @doc Pretty-prints the erlang source code corresponding to Forms into Out
%%
-spec pp_src(forms(), string()) ->
ok.
pp_src(Res, F) ->
parse_trans_pp:pp_src(Res, F).
%% Str = [io_lib:fwrite("~s~n",
%% [lists:flatten([erl_pp:form(Fm) ||
%% Fm <- revert(Res)])])],
%% file:write_file(F, list_to_binary(Str)).
%% @spec (Beam::file:filename()) -> string() | {error, Reason}
%%
%% @doc
%% Reads debug_info from the beam file Beam and returns a string containing
%% the pretty-printed corresponding erlang source code.
%% @end
-spec pp_beam(file:filename()) -> ok.
pp_beam(Beam) ->
parse_trans_pp:pp_beam(Beam).
%% @spec (Beam::filename(), Out::filename()) -> ok | {error, Reason}
%%
%% @doc
%% Reads debug_info from the beam file Beam and pretty-prints it as
%% Erlang source code, storing it in the file Out.
%% @end
%%
-spec pp_beam(file:filename(), file:filename()) -> ok.
pp_beam(F, Out) ->
parse_trans_pp:pp_beam(F, Out).
%%% @spec (File) -> Forms
%%%
%%% @doc
%%% <p>Fetches a Syntax Tree representing the code before pre-processing,
%%% that is, including record and macro definitions. Note that macro
%%% definitions must be syntactically complete forms (this function
%%% uses epp_dodger).</p>
%%% @end
%%%
-spec get_orig_syntax_tree(string()) ->
forms().
get_orig_syntax_tree(File) ->
case epp_dodger:parse_file(File) of
{ok, Forms} ->
Forms;
Err ->
erlang:error(error_reading_file, ?HERE, [{File,Err}])
end.
%%% @spec (Tree) -> Forms
%%%
%%% @doc Reverts back from Syntax Tools format to Erlang forms.
%%% <p>Note that the Erlang forms are a subset of the Syntax Tools
%%% syntax tree, so this function is safe to call even on a list of
%%% regular Erlang forms.</p>
%%% <p>Note2: R16B03 introduced a bug, where forms produced by
%%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter.
%%% This function works around that limitation, after first verifying that it's
%%% necessary to do so. Use of the workaround can be forced with the help of
%%% the `parse_trans' environment variable {revert_workaround, true}. This
%%% variable will be removed when R16B03 is no longer 'supported'.</p>
%%% @end
%%%
-spec revert(forms()) ->
forms().
revert(Tree) when is_list(Tree) ->
WorkAround = needs_revert_workaround(),
[revert_form(T, WorkAround) || T <- lists:flatten(Tree)].
%%% @spec (Tree) -> Form
%%%
%%% @doc Reverts a single form back from Syntax Tools format to Erlang forms.
%%% <p>`erl_syntax:revert/1' has had a long-standing bug where it doesn't
%%% completely revert attribute forms. This function deals properly with those
%%% cases.</p>
%%% <p>Note that the Erlang forms are a subset of the Syntax Tools
%%% syntax tree, so this function is safe to call even on a regular Erlang
%%% form.</p>
%%% <p>Note2: R16B03 introduced a bug, where forms produced by
%%% `erl_syntax:revert/1' (specifically, implicit funs) could crash the linter.
%%% This function works around that limitation, after first verifying that it's
%%% necessary to do so. Use of the workaround can be forced with the help of
%%% the `parse_trans' environment variable {revert_workaround, true}. This
%%% variable will be removed when R16B03 is no longer 'supported'.</p>
%%% @end
revert_form(F) ->
revert_form(F, needs_revert_workaround()).
revert_form(F, W) ->
case erl_syntax:revert(F) of
{attribute,L,A,Tree} when element(1,Tree) == tree ->
{attribute,L,A,erl_syntax:revert(Tree)};
Result ->
if W -> fix_impl_fun(Result);
true -> Result
end
end.
fix_impl_fun({'fun',L,{function,{atom,_,Fn},{integer,_,Ay}}}) ->
{'fun',L,{function,Fn,Ay}};
fix_impl_fun({'fun',L,{function,{atom,_,M},{atom,_,Fn},{integer,_,Ay}}}) ->
{'fun',L,{function,M,Fn,Ay}};
fix_impl_fun(T) when is_tuple(T) ->
list_to_tuple([fix_impl_fun(F) || F <- tuple_to_list(T)]);
fix_impl_fun([H|T]) ->
[fix_impl_fun(H) | fix_impl_fun(T)];
fix_impl_fun(X) ->
X.
needs_revert_workaround() ->
case application:get_env(parse_trans,revert_workaround) of
{ok, Bool} when is_boolean(Bool) -> Bool;
_ ->
Res = try lint_reverted()
catch
error:_ ->
true
end,
application:set_env(parse_trans,revert_workaround,Res),
Res
end.
lint_reverted() ->
Ts = [{attribute,1,module,m},
{attribute,2,export,[{f,0}]},
erl_syntax:function(erl_syntax:atom(f),
[erl_syntax:clause(
[],
[erl_syntax:implicit_fun(
erl_syntax:atom(f),
erl_syntax:integer(0))])])],
Rev = erl_syntax:revert_forms(Ts),
erl_lint:module(Rev),
false.
%%% @spec (Forms, Context) -> Forms | {error,Es,Ws} | {warnings,Forms,Ws}
%%%
%%% @doc Checks the transformed result for errors and warnings
%%% <p>Errors and warnings can be produced from inside a parse transform, with
%%% a bit of care. The easiest way is to simply produce an `{error, Err}' or
%%% `{warning, Warn}' form in place. This function finds such forms, and
%%% removes them from the form list (otherwise, the linter will crash), and
%%% produces a return value that the compiler can work with.</p>
%%%
%%% The format of the `error' and `warning' "forms" must be
%%% `{Tag, {Pos, Module, Info}}', where:
%%% <ul>
%%% <li>`Tag :: error | warning'</li>
%%% <li>`Pos :: LineNumber | {LineNumber, ColumnNumber}'</li>
%%% <li>`Module' is a module that exports a corresponding
%%% `Module:format_error(Info)'</li>
%%% <li>`Info :: term()'</li>
%%% </ul>
%%% <p>If the error is in the form of a caught exception, `Info' may be produced
%%% using the function {@link format_exception/2}.</p>
%%% @end
return(Forms, Context) ->
JustForms = plain_transform(
fun({error,_}) -> skip;
({warning,_}) -> skip;
(_) -> continue
end, Forms),
File = case Context of
#context{file = F} -> F;
_ -> "parse_transform"
end,
case {find_forms(Forms, error), find_forms(Forms, warning)} of
{[], []} ->
JustForms;
{[], Ws} ->
{warnings, JustForms, [{File, [W || {warning,W} <- Ws]}]};
{Es, Ws} ->
{error,
[{File, [E || {error,E} <- Es]}],
[{File, [W || {warning,W} <- Ws]}]}
end.
find_forms([H|T], Tag) when element(1, H) == Tag ->
[H|find_forms(T, Tag)];
find_forms([H|T], Tag) when is_tuple(H) ->
find_forms(tuple_to_list(H), Tag) ++ find_forms(T, Tag);
find_forms([H|T], Tag) when is_list(H) ->
find_forms(H, Tag) ++ find_forms(T, Tag);
find_forms([_|T], Tag) ->
find_forms(T, Tag);
find_forms([], _) ->
[].
-define(LINEMAX, 5).
-define(CHAR_MAX, 60).
%%% @spec (Class, Reason) -> String
%%% @equiv format_exception(Class, Reason, 4)
format_exception(Class, Reason) ->
format_exception(Class, Reason, 4).
%%% @spec (Class, Reason, Lines) -> String
%%% Class = error | throw | exit
%%% Reason = term()
%%% Lines = integer() | infinity
%%%
%%% @doc Produces a few lines of user-friendly formatting of exception info
%%%
%%% This function is very similar to the exception pretty-printing in the shell,
%%% but returns a string that can be used as error info e.g. by error forms
%%% handled by {@link return/2}. By default, the first 4 lines of the
%%% pretty-printed exception info are returned, but this can be controlled
%%% with the `Lines' parameter.
%%%
%%% Note that a stacktrace is generated inside this function.
%%% @end
format_exception(Class, Reason, Lines) ->
{current_stacktrace, ST} = erlang:process_info(self(), current_stacktrace),
PrintF = fun(Term, I) ->
io_lib_pretty:print(
Term, I, columns(), ?LINEMAX, ?CHAR_MAX,
record_print_fun())
end,
StackF = fun(_, _, _) -> false end,
lines(Lines, erl_error:format_exception(
1, Class, Reason, ST, StackF, PrintF)).
columns() ->
case io:columns() of
{ok, N} -> N;
_-> 80
end.
lines(infinity, S) -> S;
lines(N, S) ->
[L1|Ls] = re:split(iolist_to_binary([S]), <<"\n">>, [{return,list}]),
[L1|["\n" ++ L || L <- lists:sublist(Ls, 1, N-1)]].
record_print_fun() ->
fun(_,_) -> no end.
%%% @spec (Attr, Context) -> any()
%%% Attr = module | function | arity | options
%%%
%%% @doc
%%% Accessor function for the Context record.
%%% @end
-spec context(atom(), #context{}) ->
term().
context(module, #context{module = M} ) -> M;
context(function, #context{function = F}) -> F;
context(arity, #context{arity = A} ) -> A;
context(file, #context{file = F} ) -> F;
context(options, #context{options = O} ) -> O.
-spec do_inspect(insp_f(), term(), forms(), #context{}) ->
term().
do_inspect(F, Acc, Forms, Context) ->
F1 =
fun(Form, Acc0) ->
Type = type(Form),
{Recurse, Acc1} = apply_F(F, Type, Form, Context, Acc0),
if_recurse(
Recurse, Form, _Else = Acc1,
fun(ListOfLists) ->
lists:foldl(
fun(L, AccX) ->
do_inspect(
F, AccX, L,
update_context(Form, Context))
end, Acc1, ListOfLists)
end)
end,
lists:foldl(F1, Acc, Forms).
if_recurse(true, Form, Else, F) -> recurse(Form, Else, F);
if_recurse(false, _, Else, _) -> Else.
recurse(Form, Else, F) ->
case erl_syntax:subtrees(Form) of
[] ->
Else;
[_|_] = ListOfLists ->
F(ListOfLists)
end.
-spec do_transform(xform_f_rec(), term(), forms(), #context{}) ->
{forms(), term()}.
do_transform(F, Acc, Forms, Context) ->
Rec = fun do_transform/4, % this function
F1 =
fun(Form, Acc0) ->
{Before1, Form1, After1, Recurse, Acc1} =
this_form_rec(F, Form, Context, Acc0),
if Recurse ->
{NewForm, NewAcc} =
enter_subtrees(Form1, F,
update_context(Form1, Context), Acc1, Rec),
{Before1, NewForm, After1, NewAcc};
true ->
{Before1, Form1, After1, Acc1}
end
end,
mapfoldl(F1, Acc, Forms).
-spec do_depth_first(xform_f_df(), term(), forms(), #context{}) ->
{forms(), term()}.
do_depth_first(F, Acc, Forms, Context) ->
Rec = fun do_depth_first/4, % this function
F1 =
fun(Form, Acc0) ->
{NewForm, NewAcc} =
enter_subtrees(Form, F, Context, Acc0, Rec),
this_form_df(F, NewForm, Context, NewAcc)
end,
mapfoldl(F1, Acc, Forms).
enter_subtrees(Form, F, Context, Acc, Recurse) ->
case erl_syntax:subtrees(Form) of
[] ->
{Form, Acc};
[_|_] = ListOfLists ->
{NewListOfLists, NewAcc} =
mapfoldl(
fun(L, AccX) ->
Recurse(F, AccX, L, Context)
end, Acc, ListOfLists),
NewForm =
erl_syntax:update_tree(
Form, NewListOfLists),
{NewForm, NewAcc}
end.
this_form_rec(F, Form, Context, Acc) ->
Type = type(Form),
case apply_F(F, Type, Form, Context, Acc) of
{Form1x, Rec1x, A1x} ->
{[], Form1x, [], Rec1x, A1x};
{_Be1, _F1, _Af1, _Rec1, _Ac1} = Res1 ->
Res1
end.
this_form_df(F, Form, Context, Acc) ->
Type = type(Form),
case apply_F(F, Type, Form, Context, Acc) of
{Form1x, A1x} ->
{[], Form1x, [], A1x};
{_Be1, _F1, _Af1, _Ac1} = Res1 ->
Res1
end.
apply_F(F, Type, Form, Context, Acc) ->
try F(Type, Form, Context, Acc)
catch
error:Reason:ST ->
?ERROR(Reason,
?HERE,
[{type, Type},
{context, Context},
{acc, Acc},
{apply_f, F},
{form, Form},
{stack, ST}],
ST)
end.
update_context(Form, Context0) ->
case type(Form) of
function ->
{Fun, Arity} =
erl_syntax_lib:analyze_function(Form),
Context0#context{function = Fun,
arity = Arity};
_ ->
Context0
end.
%%% Slightly modified version of lists:mapfoldl/3
%%% Here, F/2 is able to insert forms before and after the form
%%% in question. The inserted forms are not transformed afterwards.
mapfoldl(F, Accu0, [Hd|Tail]) ->
{Before, Res, After, Accu1} =
case F(Hd, Accu0) of
{Be, _, Af, _} = Result when is_list(Be), is_list(Af) ->
Result;
{R1, A1} ->
{[], R1, [], A1}
end,
{Rs, Accu2} = mapfoldl(F, Accu1, Tail),
{Before ++ [Res| After ++ Rs], Accu2};
mapfoldl(F, Accu, []) when is_function(F, 2) -> {[], Accu}.
rpt_error(_Reason, _Fun, _Info, _Trace) ->
%% Fmt = lists:flatten(
%% ["*** ERROR in parse_transform function:~n"
%% "*** Reason = ~p~n",
%% "*** Location: ~p~n",
%% "*** Trace: ~p~n",
%% ["*** ~10w = ~p~n" || _ <- Info]]),
%% Args = [Reason, Fun, Trace |
%% lists:foldr(
%% fun({K,V}, Acc) ->
%% [K, V | Acc]
%% end, [], Info)],
%%io:format(Fmt, Args),
ok.
-spec format_error({atom(), term()}) ->
iolist().
format_error({E, [{M,F,A}|_]} = Error) ->
try lists:flatten(io_lib:fwrite("~p in ~s:~s/~s", [E, atom_to_list(M),
atom_to_list(F), integer_to_list(A)]))
catch
error:_ ->
format_error_(Error)
end;
format_error(Error) ->
format_error_(Error).
format_error_(Error) ->
lists:flatten(io_lib:fwrite("~p", [Error])).
%% EUnit
-ifdef(TEST).
format_exeption_test() ->
[_,_,_] = format_exception(error, {error, foo}, 3),
ok.
-endif.