diff options
Diffstat (limited to 'lib/syntax_tools/src/erl_syntax_lib.erl')
-rw-r--r-- | lib/syntax_tools/src/erl_syntax_lib.erl | 2168 |
1 files changed, 2168 insertions, 0 deletions
diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl new file mode 100644 index 0000000000..ccbf864c2a --- /dev/null +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -0,0 +1,2168 @@ +%% ===================================================================== +%% This library is free software; you can redistribute it and/or modify +%% it under the terms of the GNU Lesser General Public License as +%% published by the Free Software Foundation; either version 2 of the +%% License, or (at your option) any later version. +%% +%% This library is distributed in the hope that it will be useful, but +%% WITHOUT ANY WARRANTY; without even the implied warranty of +%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%% Lesser General Public License for more details. +%% +%% You should have received a copy of the GNU Lesser General Public +%% License along with this library; if not, write to the Free Software +%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +%% USA +%% +%% $Id$ +%% +%% @copyright 1997-2006 Richard Carlsson +%% @author Richard Carlsson <[email protected]> +%% @end +%% ===================================================================== + +%% @doc Support library for abstract Erlang syntax trees. +%% +%% This module contains utility functions for working with the +%% abstract data type defined in the module {@link erl_syntax}. +%% +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. + +-module(erl_syntax_lib). + +-export([analyze_application/1, analyze_attribute/1, + analyze_export_attribute/1, analyze_file_attribute/1, + analyze_form/1, analyze_forms/1, analyze_function/1, + analyze_function_name/1, analyze_implicit_fun/1, + analyze_import_attribute/1, analyze_module_attribute/1, + analyze_record_attribute/1, analyze_record_expr/1, + analyze_record_field/1, analyze_rule/1, + analyze_wild_attribute/1, annotate_bindings/1, + annotate_bindings/2, fold/3, fold_subtrees/3, foldl_listlist/3, + function_name_expansions/1, is_fail_expr/1, limit/2, limit/3, + map/2, map_subtrees/2, mapfold/3, mapfold_subtrees/3, + mapfoldl_listlist/3, new_variable_name/1, new_variable_name/2, + new_variable_names/2, new_variable_names/3, strip_comments/1, + to_comment/1, to_comment/2, to_comment/3, variables/1]). + + +%% ===================================================================== +%% @spec map(Function, Tree::syntaxTree()) -> syntaxTree() +%% +%% Function = (syntaxTree()) -> syntaxTree() +%% +%% @doc Applies a function to each node of a syntax tree. The result of +%% each application replaces the corresponding original node. The order +%% of traversal is bottom-up. +%% +%% @see map_subtrees/2 + +map(F, Tree) -> + case erl_syntax:subtrees(Tree) of + [] -> + F(Tree); + Gs -> + Tree1 = erl_syntax:make_tree(erl_syntax:type(Tree), + [[map(F, T) || T <- G] + || G <- Gs]), + F(erl_syntax:copy_attrs(Tree, Tree1)) + end. + + +%% ===================================================================== +%% @spec map_subtrees(Function, syntaxTree()) -> syntaxTree() +%% +%% Function = (Tree) -> Tree1 +%% +%% @doc Applies a function to each immediate subtree of a syntax tree. +%% The result of each application replaces the corresponding original +%% node. +%% +%% @see map/2 + +map_subtrees(F, Tree) -> + case erl_syntax:subtrees(Tree) of + [] -> + Tree; + Gs -> + Tree1 = erl_syntax:make_tree(erl_syntax:type(Tree), + [[F(T) || T <- G] || G <- Gs]), + erl_syntax:copy_attrs(Tree, Tree1) + end. + + +%% ===================================================================== +%% @spec fold(Function, Start::term(), Tree::syntaxTree()) -> term() +%% +%% Function = (syntaxTree(), term()) -> term() +%% +%% @doc Folds a function over all nodes of a syntax tree. The result is +%% the value of `Function(X1, Function(X2, ... Function(Xn, Start) +%% ... ))', where `[X1, X2, ..., Xn]' are the nodes of +%% `Tree' in a post-order traversal. +%% +%% @see fold_subtrees/3 +%% @see foldl_listlist/3 + +fold(F, S, Tree) -> + case erl_syntax:subtrees(Tree) of + [] -> + F(Tree, S); + Gs -> + F(Tree, fold_1(F, S, Gs)) + end. + +fold_1(F, S, [L | Ls]) -> + fold_1(F, fold_2(F, S, L), Ls); +fold_1(_, S, []) -> + S. + +fold_2(F, S, [T | Ts]) -> + fold_2(F, fold(F, S, T), Ts); +fold_2(_, S, []) -> + S. + + +%% ===================================================================== +%% @spec fold_subtrees(Function, Start::term(), Tree::syntaxTree()) -> +%% term() +%% +%% Function = (syntaxTree(), term()) -> term() +%% +%% @doc Folds a function over the immediate subtrees of a syntax tree. +%% This is similar to `fold/3', but only on the immediate +%% subtrees of `Tree', in left-to-right order; it does not +%% include the root node of `Tree'. +%% +%% @see fold/3 + +fold_subtrees(F, S, Tree) -> + foldl_listlist(F, S, erl_syntax:subtrees(Tree)). + + +%% ===================================================================== +%% @spec foldl_listlist(Function, Start::term(), [[term()]]) -> term() +%% +%% Function = (term(), term()) -> term() +%% +%% @doc Like `lists:foldl/3', but over a list of lists. +%% +%% @see fold/3 +%% @see //stdlib/lists:foldl/3 + +foldl_listlist(F, S, [L | Ls]) -> + foldl_listlist(F, foldl(F, S, L), Ls); +foldl_listlist(_, S, []) -> + S. + +foldl(F, S, [T | Ts]) -> + foldl(F, F(T, S), Ts); +foldl(_, S, []) -> + S. + + +%% ===================================================================== +%% @spec mapfold(Function, Start::term(), Tree::syntaxTree()) -> +%% {syntaxTree(), term()} +%% +%% Function = (syntaxTree(), term()) -> {syntaxTree(), term()} +%% +%% @doc Combines map and fold in a single operation. This is similar to +%% `map/2', but also propagates an extra value from each +%% application of the `Function' to the next, while doing a +%% post-order traversal of the tree like `fold/3'. The value +%% `Start' is passed to the first function application, and +%% the final result is the result of the last application. +%% +%% @see map/2 +%% @see fold/3 + +mapfold(F, S, Tree) -> + case erl_syntax:subtrees(Tree) of + [] -> + F(Tree, S); + Gs -> + {Gs1, S1} = mapfold_1(F, S, Gs), + Tree1 = erl_syntax:make_tree(erl_syntax:type(Tree), Gs1), + F(erl_syntax:copy_attrs(Tree, Tree1), S1) + end. + +mapfold_1(F, S, [L | Ls]) -> + {L1, S1} = mapfold_2(F, S, L), + {Ls1, S2} = mapfold_1(F, S1, Ls), + {[L1 | Ls1], S2}; +mapfold_1(_, S, []) -> + {[], S}. + +mapfold_2(F, S, [T | Ts]) -> + {T1, S1} = mapfold(F, S, T), + {Ts1, S2} = mapfold_2(F, S1, Ts), + {[T1 | Ts1], S2}; +mapfold_2(_, S, []) -> + {[], S}. + + +%% ===================================================================== +%% @spec mapfold_subtrees(Function, Start::term(), +%% Tree::syntaxTree()) -> {syntaxTree(), term()} +%% +%% Function = (syntaxTree(), term()) -> {syntaxTree(), term()} +%% +%% @doc Does a mapfold operation over the immediate subtrees of a syntax +%% tree. This is similar to `mapfold/3', but only on the +%% immediate subtrees of `Tree', in left-to-right order; it +%% does not include the root node of `Tree'. +%% +%% @see mapfold/3 + +mapfold_subtrees(F, S, Tree) -> + case erl_syntax:subtrees(Tree) of + [] -> + {Tree, S}; + Gs -> + {Gs1, S1} = mapfoldl_listlist(F, S, Gs), + Tree1 = erl_syntax:make_tree(erl_syntax:type(Tree), Gs1), + {erl_syntax:copy_attrs(Tree, Tree1), S1} + end. + + +%% ===================================================================== +%% @spec mapfoldl_listlist(Function, State, [[term()]]) -> +%% {[[term()]], term()} +%% +%% Function = (term(), term()) -> {term(), term()} +%% +%% @doc Like `lists:mapfoldl/3', but over a list of lists. +%% The list of lists in the result has the same structure as the given +%% list of lists. + +mapfoldl_listlist(F, S, [L | Ls]) -> + {L1, S1} = mapfoldl(F, S, L), + {Ls1, S2} = mapfoldl_listlist(F, S1, Ls), + {[L1 | Ls1], S2}; +mapfoldl_listlist(_, S, []) -> + {[], S}. + +mapfoldl(F, S, [L | Ls]) -> + {L1, S1} = F(L, S), + {Ls1, S2} = mapfoldl(F, S1, Ls), + {[L1 | Ls1], S2}; +mapfoldl(_, S, []) -> + {[], S}. + + +%% ===================================================================== +%% @spec variables(syntaxTree()) -> set(atom()) +%% +%% set(T) = //stdlib/sets:set(T) +%% +%% @doc Returns the names of variables occurring in a syntax tree, The +%% result is a set of variable names represented by atoms. Macro names +%% are not included. +%% +%% @see //stdlib/sets + +variables(Tree) -> + variables(Tree, sets:new()). + +variables(T, S) -> + case erl_syntax:type(T) of + variable -> + sets:add_element(erl_syntax:variable_name(T), S); + macro -> + %% macro names are ignored, even if represented by variables + case erl_syntax:macro_arguments(T) of + none -> S; + As -> + variables_2(As, S) + end; + _ -> + case erl_syntax:subtrees(T) of + [] -> + S; + Gs -> + variables_1(Gs, S) + end + end. + +variables_1([L | Ls], S) -> + variables_1(Ls, variables_2(L, S)); +variables_1([], S) -> + S. + +variables_2([T | Ts], S) -> + variables_2(Ts, variables(T, S)); +variables_2([], S) -> + S. + + +-define(MINIMUM_RANGE, 100). +-define(START_RANGE_FACTOR, 100). +-define(MAX_RETRIES, 3). % retries before enlarging range +-define(ENLARGE_ENUM, 8). % range enlargment enumerator +-define(ENLARGE_DENOM, 1). % range enlargment denominator + +default_variable_name(N) -> + list_to_atom("V" ++ integer_to_list(N)). + +%% ===================================================================== +%% @spec new_variable_name(Used::set(atom())) -> atom() +%% +%% @doc Returns an atom which is not already in the set `Used'. This is +%% equivalent to `new_variable_name(Function, Used)', where `Function' +%% maps a given integer `N' to the atom whose name consists of "`V'" +%% followed by the numeral for `N'. +%% +%% @see new_variable_name/2 + +new_variable_name(S) -> + new_variable_name(fun default_variable_name/1, S). + +%% ===================================================================== +%% @spec new_variable_name(Function, Used::set(atom())) -> atom() +%% +%% Function = (integer()) -> atom() +%% +%% @doc Returns a user-named atom which is not already in the set +%% `Used'. The atom is generated by applying the given +%% `Function' to a generated integer. Integers are generated +%% using an algorithm which tries to keep the names randomly distributed +%% within a reasonably small range relative to the number of elements in +%% the set. +%% +%% This function uses the module `random' to generate new +%% keys. The seed it uses may be initialized by calling +%% `random:seed/0' or `random:seed/3' before this +%% function is first called. +%% +%% @see new_variable_name/1 +%% @see //stdlib/sets +%% @see //stdlib/random + +new_variable_name(F, S) -> + R = start_range(S), + new_variable_name(R, F, S). + +new_variable_name(R, F, S) -> + new_variable_name(generate(R, R), R, 0, F, S). + +new_variable_name(N, R, T, F, S) when T < ?MAX_RETRIES -> + A = F(N), + case sets:is_element(A, S) of + true -> + new_variable_name(generate(N, R), R, T + 1, F, S); + false -> + A + end; +new_variable_name(N, R, _T, F, S) -> + %% Too many retries - enlarge the range and start over. + R1 = (R * ?ENLARGE_ENUM) div ?ENLARGE_DENOM, + new_variable_name(generate(N, R1), R1, 0, F, S). + +%% Note that we assume that it is very cheap to take the size of +%% the given set. This should be valid for the stdlib +%% implementation of `sets'. + +start_range(S) -> + max(sets:size(S) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE). + +max(X, Y) when X > Y -> X; +max(_, Y) -> Y. + +%% The previous number might or might not be used to compute the +%% next number to be tried. It is currently not used. +%% +%% It is important that this function does not generate values in +%% order, but (pseudo-)randomly distributed over the range. + +generate(_Key, Range) -> + random:uniform(Range). % works well + + +%% ===================================================================== +%% @spec new_variable_names(N::integer(), Used::set(atom())) -> [atom()] +%% +%% @doc Like `new_variable_name/1', but generates a list of +%% `N' new names. +%% +%% @see new_variable_name/1 + +new_variable_names(N, S) -> + new_variable_names(N, fun default_variable_name/1, S). + +%% ===================================================================== +%% @spec new_variable_names(N::integer(), Function, +%% Used::set(atom())) -> [atom()] +%% +%% Function = (integer()) -> atom() +%% +%% @doc Like `new_variable_name/2', but generates a list of +%% `N' new names. +%% +%% @see new_variable_name/2 + +new_variable_names(N, F, S) when is_integer(N) -> + R = start_range(S), + new_variable_names(N, [], R, F, S). + +new_variable_names(N, Names, R, F, S) when N > 0 -> + Name = new_variable_name(R, F, S), + S1 = sets:add_element(Name, S), + new_variable_names(N - 1, [Name | Names], R, F, S1); +new_variable_names(0, Names, _, _, _) -> + Names. + + +%% ===================================================================== +%% @spec annotate_bindings(Tree::syntaxTree(), +%% Bindings::ordset(atom())) -> syntaxTree() +%% +%% @type ordset(T) = //stdlib/ordsets:ordset(T) +%% +%% @doc Adds or updates annotations on nodes in a syntax tree. +%% `Bindings' specifies the set of bound variables in the +%% environment of the top level node. The following annotations are +%% affected: +%% <ul> +%% <li>`{env, Vars}', representing the input environment +%% of the subtree.</li> +%% +%% <li>`{bound, Vars}', representing the variables that +%% are bound in the subtree.</li> +%% +%% <li>`{free, Vars}', representing the free variables in +%% the subtree.</li> +%% </ul> +%% `Bindings' and `Vars' are ordered-set lists +%% (cf. module `ordsets') of atoms representing variable +%% names. +%% +%% @see annotate_bindings/1 +%% @see //stdlib/ordsets + +annotate_bindings(Tree, Env) -> + {Tree1, _, _} = vann(Tree, Env), + Tree1. + +%% ===================================================================== +%% @spec annotate_bindings(Tree::syntaxTree()) -> syntaxTree() +%% +%% @doc Adds or updates annotations on nodes in a syntax tree. +%% Equivalent to `annotate_bindings(Tree, Bindings)' where +%% the top-level environment `Bindings' is taken from the +%% annotation `{env, Bindings}' on the root node of +%% `Tree'. An exception is thrown if no such annotation +%% should exist. +%% +%% @see annotate_bindings/2 + +annotate_bindings(Tree) -> + As = erl_syntax:get_ann(Tree), + case lists:keyfind(env, 1, As) of + {env, InVars} -> + annotate_bindings(Tree, InVars); + _ -> + erlang:error(badarg) + end. + +vann(Tree, Env) -> + case erl_syntax:type(Tree) of + variable -> + %% Variable use + Bound = [], + Free = [erl_syntax:variable_name(Tree)], + {ann_bindings(Tree, Env, Bound, Free), Bound, Free}; + match_expr -> + vann_match_expr(Tree, Env); + case_expr -> + vann_case_expr(Tree, Env); + if_expr -> + vann_if_expr(Tree, Env); + cond_expr -> + vann_cond_expr(Tree, Env); + receive_expr -> + vann_receive_expr(Tree, Env); + catch_expr -> + vann_catch_expr(Tree, Env); + try_expr -> + vann_try_expr(Tree, Env); + function -> + vann_function(Tree, Env); + rule -> + vann_rule(Tree, Env); + fun_expr -> + vann_fun_expr(Tree, Env); + list_comp -> + vann_list_comp(Tree, Env); + binary_comp -> + vann_binary_comp(Tree, Env); + generator -> + vann_generator(Tree, Env); + binary_generator -> + vann_binary_generator(Tree, Env); + block_expr -> + vann_block_expr(Tree, Env); + macro -> + vann_macro(Tree, Env); + _Type -> + F = vann_list_join(Env), + {Tree1, {Bound, Free}} = mapfold_subtrees(F, {[], []}, + Tree), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free} + end. + +vann_list_join(Env) -> + fun (T, {Bound, Free}) -> + {T1, Bound1, Free1} = vann(T, Env), + {T1, {ordsets:union(Bound, Bound1), + ordsets:union(Free, Free1)}} + end. + +vann_list(Ts, Env) -> + lists:mapfoldl(vann_list_join(Env), {[], []}, Ts). + +vann_function(Tree, Env) -> + Cs = erl_syntax:function_clauses(Tree), + {Cs1, {_, Free}} = vann_clauses(Cs, Env), + N = erl_syntax:function_name(Tree), + {N1, _, _} = vann(N, Env), + Tree1 = rewrite(Tree, erl_syntax:function(N1, Cs1)), + Bound = [], + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_rule(Tree, Env) -> + Cs = erl_syntax:rule_clauses(Tree), + {Cs1, {_, Free}} = vann_clauses(Cs, Env), + N = erl_syntax:rule_name(Tree), + {N1, _, _} = vann(N, Env), + Tree1 = rewrite(Tree, erl_syntax:rule(N1, Cs1)), + Bound = [], + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_fun_expr(Tree, Env) -> + Cs = erl_syntax:fun_expr_clauses(Tree), + {Cs1, {_, Free}} = vann_clauses(Cs, Env), + Tree1 = rewrite(Tree, erl_syntax:fun_expr(Cs1)), + Bound = [], + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_match_expr(Tree, Env) -> + E = erl_syntax:match_expr_body(Tree), + {E1, Bound1, Free1} = vann(E, Env), + Env1 = ordsets:union(Env, Bound1), + P = erl_syntax:match_expr_pattern(Tree), + {P1, Bound2, Free2} = vann_pattern(P, Env1), + Bound = ordsets:union(Bound1, Bound2), + Free = ordsets:union(Free1, Free2), + Tree1 = rewrite(Tree, erl_syntax:match_expr(P1, E1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_case_expr(Tree, Env) -> + E = erl_syntax:case_expr_argument(Tree), + {E1, Bound1, Free1} = vann(E, Env), + Env1 = ordsets:union(Env, Bound1), + Cs = erl_syntax:case_expr_clauses(Tree), + {Cs1, {Bound2, Free2}} = vann_clauses(Cs, Env1), + Bound = ordsets:union(Bound1, Bound2), + Free = ordsets:union(Free1, Free2), + Tree1 = rewrite(Tree, erl_syntax:case_expr(E1, Cs1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_if_expr(Tree, Env) -> + Cs = erl_syntax:if_expr_clauses(Tree), + {Cs1, {Bound, Free}} = vann_clauses(Cs, Env), + Tree1 = rewrite(Tree, erl_syntax:if_expr(Cs1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_cond_expr(_Tree, _Env) -> + erlang:error({not_implemented,cond_expr}). + +vann_catch_expr(Tree, Env) -> + E = erl_syntax:catch_expr_body(Tree), + {E1, _, Free} = vann(E, Env), + Tree1 = rewrite(Tree, erl_syntax:catch_expr(E1)), + Bound = [], + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_try_expr(Tree, Env) -> + Es = erl_syntax:try_expr_body(Tree), + {Es1, {Bound1, Free1}} = vann_body(Es, Env), + Cs = erl_syntax:try_expr_clauses(Tree), + %% bindings in the body should be available in the success case, + {Cs1, {_, Free2}} = vann_clauses(Cs, ordsets:union(Env, Bound1)), + Hs = erl_syntax:try_expr_handlers(Tree), + {Hs1, {_, Free3}} = vann_clauses(Hs, Env), + %% the after part does not export anything, yet; this might change + As = erl_syntax:try_expr_after(Tree), + {As1, {_, Free4}} = vann_body(As, Env), + Tree1 = rewrite(Tree, erl_syntax:try_expr(Es1, Cs1, Hs1, As1)), + Bound = [], + Free = ordsets:union(Free1, ordsets:union(Free2, ordsets:union(Free3, Free4))), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_receive_expr(Tree, Env) -> + %% The timeout action is treated as an extra clause. + %% Bindings in the expiry expression are local only. + Cs = erl_syntax:receive_expr_clauses(Tree), + Es = erl_syntax:receive_expr_action(Tree), + C = erl_syntax:clause([], Es), + {[C1 | Cs1], {Bound, Free1}} = vann_clauses([C | Cs], Env), + Es1 = erl_syntax:clause_body(C1), + {T1, _, Free2} = case erl_syntax:receive_expr_timeout(Tree) of + none -> + {none, [], []}; + T -> + vann(T, Env) + end, + Free = ordsets:union(Free1, Free2), + Tree1 = rewrite(Tree, erl_syntax:receive_expr(Cs1, T1, Es1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_list_comp(Tree, Env) -> + Es = erl_syntax:list_comp_body(Tree), + {Es1, {Bound1, Free1}} = vann_list_comp_body(Es, Env), + Env1 = ordsets:union(Env, Bound1), + T = erl_syntax:list_comp_template(Tree), + {T1, _, Free2} = vann(T, Env1), + Free = ordsets:union(Free1, ordsets:subtract(Free2, Bound1)), + Bound = [], + Tree1 = rewrite(Tree, erl_syntax:list_comp(T1, Es1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_list_comp_body_join() -> + fun (T, {Env, Bound, Free}) -> + {T1, Bound1, Free1} = case erl_syntax:type(T) of + binary_generator -> + vann_binary_generator(T,Env); + generator -> + vann_generator(T, Env); + _ -> + %% Bindings in filters are not + %% exported to the rest of the + %% body. + {T2, _, Free2} = vann(T, Env), + {T2, [], Free2} + end, + Env1 = ordsets:union(Env, Bound1), + {T1, {Env1, ordsets:union(Bound, Bound1), + ordsets:union(Free, + ordsets:subtract(Free1, Bound))}} + end. + +vann_list_comp_body(Ts, Env) -> + F = vann_list_comp_body_join(), + {Ts1, {_, Bound, Free}} = lists:mapfoldl(F, {Env, [], []}, Ts), + {Ts1, {Bound, Free}}. + +vann_binary_comp(Tree, Env) -> + Es = erl_syntax:binary_comp_body(Tree), + {Es1, {Bound1, Free1}} = vann_binary_comp_body(Es, Env), + Env1 = ordsets:union(Env, Bound1), + T = erl_syntax:binary_comp_template(Tree), + {T1, _, Free2} = vann(T, Env1), + Free = ordsets:union(Free1, ordsets:subtract(Free2, Bound1)), + Bound = [], + Tree1 = rewrite(Tree, erl_syntax:binary_comp(T1, Es1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_binary_comp_body_join() -> + fun (T, {Env, Bound, Free}) -> + {T1, Bound1, Free1} = case erl_syntax:type(T) of + binary_generator -> + vann_binary_generator(T, Env); + generator -> + vann_generator(T, Env); + _ -> + %% Bindings in filters are not + %% exported to the rest of the + %% body. + {T2, _, Free2} = vann(T, Env), + {T2, [], Free2} + end, + Env1 = ordsets:union(Env, Bound1), + {T1, {Env1, ordsets:union(Bound, Bound1), + ordsets:union(Free, + ordsets:subtract(Free1, Bound))}} + end. + +vann_binary_comp_body(Ts, Env) -> + F = vann_binary_comp_body_join(), + {Ts1, {_, Bound, Free}} = lists:mapfoldl(F, {Env, [], []}, Ts), + {Ts1, {Bound, Free}}. + +%% In list comprehension generators, the pattern variables are always +%% viewed as new occurrences, shadowing whatever is in the input +%% environment (thus, the pattern contains no variable uses, only +%% bindings). Bindings in the generator body are not exported. + +vann_generator(Tree, Env) -> + P = erl_syntax:generator_pattern(Tree), + {P1, Bound, _} = vann_pattern(P, []), + E = erl_syntax:generator_body(Tree), + {E1, _, Free} = vann(E, Env), + Tree1 = rewrite(Tree, erl_syntax:generator(P1, E1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_binary_generator(Tree, Env) -> + P = erl_syntax:binary_generator_pattern(Tree), + {P1, Bound, _} = vann_pattern(P, Env), + E = erl_syntax:binary_generator_body(Tree), + {E1, _, Free} = vann(E, Env), + Tree1 = rewrite(Tree, erl_syntax:binary_generator(P1, E1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_block_expr(Tree, Env) -> + Es = erl_syntax:block_expr_body(Tree), + {Es1, {Bound, Free}} = vann_body(Es, Env), + Tree1 = rewrite(Tree, erl_syntax:block_expr(Es1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_body_join() -> + fun (T, {Env, Bound, Free}) -> + {T1, Bound1, Free1} = vann(T, Env), + Env1 = ordsets:union(Env, Bound1), + {T1, {Env1, ordsets:union(Bound, Bound1), + ordsets:union(Free, + ordsets:subtract(Free1, Bound))}} + end. + +vann_body(Ts, Env) -> + {Ts1, {_, Bound, Free}} = lists:mapfoldl(vann_body_join(), + {Env, [], []}, Ts), + {Ts1, {Bound, Free}}. + +%% Macro names must be ignored even if they happen to be variables, +%% lexically speaking. + +vann_macro(Tree, Env) -> + {As, {Bound, Free}} = case erl_syntax:macro_arguments(Tree) of + none -> + {none, {[], []}}; + As1 -> + vann_list(As1, Env) + end, + N = erl_syntax:macro_name(Tree), + Tree1 = rewrite(Tree, erl_syntax:macro(N, As)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_pattern(Tree, Env) -> + case erl_syntax:type(Tree) of + variable -> + V = erl_syntax:variable_name(Tree), + case ordsets:is_element(V, Env) of + true -> + %% Variable use + Bound = [], + Free = [V], + {ann_bindings(Tree, Env, Bound, Free), Bound, Free}; + false -> + %% Variable binding + Bound = [V], + Free = [], + {ann_bindings(Tree, Env, Bound, Free), Bound, Free} + end; + match_expr -> + %% Alias pattern + P = erl_syntax:match_expr_pattern(Tree), + {P1, Bound1, Free1} = vann_pattern(P, Env), + E = erl_syntax:match_expr_body(Tree), + {E1, Bound2, Free2} = vann_pattern(E, Env), + Bound = ordsets:union(Bound1, Bound2), + Free = ordsets:union(Free1, Free2), + Tree1 = rewrite(Tree, erl_syntax:match_expr(P1, E1)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}; + macro -> + %% The macro name must be ignored. The arguments are treated + %% as patterns. + {As, {Bound, Free}} = + case erl_syntax:macro_arguments(Tree) of + none -> + {none, {[], []}}; + As1 -> + vann_patterns(As1, Env) + end, + N = erl_syntax:macro_name(Tree), + Tree1 = rewrite(Tree, erl_syntax:macro(N, As)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}; + _Type -> + F = vann_patterns_join(Env), + {Tree1, {Bound, Free}} = mapfold_subtrees(F, {[], []}, + Tree), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free} + end. + +vann_patterns_join(Env) -> + fun (T, {Bound, Free}) -> + {T1, Bound1, Free1} = vann_pattern(T, Env), + {T1, {ordsets:union(Bound, Bound1), + ordsets:union(Free, Free1)}} + end. + +vann_patterns(Ps, Env) -> + lists:mapfoldl(vann_patterns_join(Env), {[], []}, Ps). + +vann_clause(C, Env) -> + {Ps, {Bound1, Free1}} = vann_patterns(erl_syntax:clause_patterns(C), + Env), + Env1 = ordsets:union(Env, Bound1), + %% Guards cannot add bindings + {G1, _, Free2} = case erl_syntax:clause_guard(C) of + none -> + {none, [], []}; + G -> + vann(G, Env1) + end, + {Es, {Bound2, Free3}} = vann_body(erl_syntax:clause_body(C), Env1), + Bound = ordsets:union(Bound1, Bound2), + Free = ordsets:union(Free1, + ordsets:subtract(ordsets:union(Free2, Free3), + Bound1)), + Tree1 = rewrite(C, erl_syntax:clause(Ps, G1, Es)), + {ann_bindings(Tree1, Env, Bound, Free), Bound, Free}. + +vann_clauses_join(Env) -> + fun (C, {Bound, Free}) -> + {C1, Bound1, Free1} = vann_clause(C, Env), + {C1, {ordsets:intersection(Bound, Bound1), + ordsets:union(Free, Free1)}} + end. + +vann_clauses([C | Cs], Env) -> + {C1, Bound, Free} = vann_clause(C, Env), + {Cs1, BF} = lists:mapfoldl(vann_clauses_join(Env), {Bound, Free}, Cs), + {[C1 | Cs1], BF}; +vann_clauses([], _Env) -> + {[], {[], []}}. + +ann_bindings(Tree, Env, Bound, Free) -> + As0 = erl_syntax:get_ann(Tree), + As1 = [{env, Env}, + {bound, Bound}, + {free, Free} + | delete_binding_anns(As0)], + erl_syntax:set_ann(Tree, As1). + +delete_binding_anns([{env, _} | As]) -> + delete_binding_anns(As); +delete_binding_anns([{bound, _} | As]) -> + delete_binding_anns(As); +delete_binding_anns([{free, _} | As]) -> + delete_binding_anns(As); +delete_binding_anns([A | As]) -> + [A | delete_binding_anns(As)]; +delete_binding_anns([]) -> + []. + + +%% ===================================================================== +%% @spec is_fail_expr(Tree::syntaxTree()) -> bool() +%% +%% @doc Returns `true' if `Tree' represents an +%% expression which never terminates normally. Note that the reverse +%% does not apply. Currently, the detected cases are calls to +%% `exit/1', `throw/1', +%% `erlang:error/1' and `erlang:error/2'. +%% +%% @see //erts/erlang:exit/1 +%% @see //erts/erlang:throw/1 +%% @see //erts/erlang:error/1 +%% @see //erts/erlang:error/2 + +is_fail_expr(E) -> + case erl_syntax:type(E) of + application -> + N = length(erl_syntax:application_arguments(E)), + F = erl_syntax:application_operator(E), + case catch {ok, analyze_function_name(F)} of + syntax_error -> + false; + {ok, exit} when N =:= 1 -> + true; + {ok, throw} when N =:= 1 -> + true; + {ok, {erlang, exit}} when N =:= 1 -> + true; + {ok, {erlang, throw}} when N =:= 1 -> + true; + {ok, {erlang, error}} when N =:= 1 -> + true; + {ok, {erlang, error}} when N =:= 2 -> + true; + {ok, {erlang, fault}} when N =:= 1 -> + true; + {ok, {erlang, fault}} when N =:= 2 -> + true; + _ -> + false + end; + _ -> + false + end. + + +%% ===================================================================== +%% @spec analyze_forms(Forms) -> [{Key, term()}] +%% +%% Forms = syntaxTree() | [syntaxTree()] +%% Key = attributes | errors | exports | functions | imports +%% | module | records | rules | warnings +%% +%% @doc Analyzes a sequence of "program forms". The given +%% `Forms' may be a single syntax tree of type +%% `form_list', or a list of "program form" syntax trees. The +%% returned value is a list of pairs `{Key, Info}', where +%% each value of `Key' occurs at most once in the list; the +%% absence of a particular key indicates that there is no well-defined +%% value for that key. +%% +%% Each entry in the resulting list contains the following +%% corresponding information about the program forms: +%% <dl> +%% <dt>`{attributes, Attributes}'</dt> +%% <dd><ul> +%% <li>`Attributes = [{atom(), term()}]'</li> +%% </ul> +%% `Attributes' is a list of pairs representing the +%% names and corresponding values of all so-called "wild" +%% attributes (as e.g. "`-compile(...)'") occurring in +%% `Forms' (cf. `analyze_wild_attribute/1'). +%% We do not guarantee that each name occurs at most once in the +%% list. The order of listing is not defined.</dd> +%% +%% <dt>`{errors, Errors}'</dt> +%% <dd><ul> +%% <li>`Errors = [term()]'</li> +%% </ul> +%% `Errors' is the list of error descriptors of all +%% `error_marker' nodes that occur in +%% `Forms'. The order of listing is not defined.</dd> +%% +%% <dt>`{exports, Exports}'</dt> +%% <dd><ul> +%% <li>`Exports = [FunctionName]'</li> +%% <li>`FunctionName = atom() +%% | {atom(), integer()} +%% | {ModuleName, FunctionName}'</li> +%% <li>`ModuleName = atom()'</li> +%% </ul> +%% `Exports' is a list of representations of those +%% function names that are listed by export declaration attributes +%% in `Forms' (cf. +%% `analyze_export_attribute/1'). We do not guarantee +%% that each name occurs at most once in the list. The order of +%% listing is not defined.</dd> +%% +%% <dt>`{functions, Functions}'</dt> +%% <dd><ul> +%% <li>`Functions = [{atom(), integer()}]'</li> +%% </ul> +%% `Functions' is a list of the names of the functions +%% that are defined in `Forms' (cf. +%% `analyze_function/1'). We do not guarantee that each +%% name occurs at most once in the list. The order of listing is +%% not defined.</dd> +%% +%% <dt>`{imports, Imports}'</dt> +%% <dd><ul> +%% <li>`Imports = [{Module, Names}]'</li> +%% <li>`Module = atom()'</li> +%% <li>`Names = [FunctionName]'</li> +%% <li>`FunctionName = atom() +%% | {atom(), integer()} +%% | {ModuleName, FunctionName}'</li> +%% <li>`ModuleName = atom()'</li> +%% </ul> +%% `Imports' is a list of pairs representing those +%% module names and corresponding function names that are listed +%% by import declaration attributes in `Forms' (cf. +%% `analyze_import_attribute/1'), where each +%% `Module' occurs at most once in +%% `Imports'. We do not guarantee that each name occurs +%% at most once in the lists of function names. The order of +%% listing is not defined.</dd> +%% +%% <dt>`{module, ModuleName}'</dt> +%% <dd><ul> +%% <li>`ModuleName = atom()'</li> +%% </ul> +%% `ModuleName' is the name declared by a module +%% attribute in `Forms'. If no module name is defined +%% in `Forms', the result will contain no entry for the +%% `module' key. If multiple module name declarations +%% should occur, all but the first will be ignored.</dd> +%% +%% <dt>`{records, Records}'</dt> +%% <dd><ul> +%% <li>`Records = [{atom(), Fields}]'</li> +%% <li>`Fields = [{atom(), Default}]'</li> +%% <li>`Default = none | syntaxTree()'</li> +%% </ul> +%% `Records' is a list of pairs representing the names +%% and corresponding field declarations of all record declaration +%% attributes occurring in `Forms'. For fields declared +%% without a default value, the corresponding value for +%% `Default' is the atom `none' (cf. +%% `analyze_record_attribute/1'). We do not guarantee +%% that each record name occurs at most once in the list. The +%% order of listing is not defined.</dd> +%% +%% <dt>`{rules, Rules}'</dt> +%% <dd><ul> +%% <li>`Rules = [{atom(), integer()}]'</li> +%% </ul> +%% `Rules' is a list of the names of the rules that are +%% defined in `Forms' (cf. +%% `analyze_rule/1'). We do not guarantee that each +%% name occurs at most once in the list. The order of listing is +%% not defined.</dd> +%% +%% <dt>`{warnings, Warnings}'</dt> +%% <dd><ul> +%% <li>`Warnings = [term()]'</li> +%% </ul> +%% `Warnings' is the list of error descriptors of all +%% `warning_marker' nodes that occur in +%% `Forms'. The order of listing is not defined.</dd> +%% </dl> +%% +%% The evaluation throws `syntax_error' if an ill-formed +%% Erlang construct is encountered. +%% +%% @see analyze_wild_attribute/1 +%% @see analyze_export_attribute/1 +%% @see analyze_import_attribute/1 +%% @see analyze_record_attribute/1 +%% @see analyze_function/1 +%% @see analyze_rule/1 +%% @see erl_syntax:error_marker_info/1 +%% @see erl_syntax:warning_marker_info/1 + +analyze_forms(Forms) when is_list(Forms) -> + finfo_to_list(lists:foldl(fun collect_form/2, new_finfo(), Forms)); +analyze_forms(Forms) -> + analyze_forms( + erl_syntax:form_list_elements( + erl_syntax:flatten_form_list(Forms))). + +collect_form(Node, Info) -> + case analyze_form(Node) of + {attribute, {Name, Data}} -> + collect_attribute(Name, Data, Info); + {attribute, preprocessor} -> + Info; + {function, Name} -> + finfo_add_function(Name, Info); + {rule, Name} -> + finfo_add_rule(Name, Info); + {error_marker, Data} -> + finfo_add_error(Data, Info); + {warning_marker, Data} -> + finfo_add_warning(Data, Info); + _ -> + Info + end. + +collect_attribute(module, M, Info) -> + finfo_set_module(M, Info); +collect_attribute(export, L, Info) -> + finfo_add_exports(L, Info); +collect_attribute(import, {M, L}, Info) -> + finfo_add_imports(M, L, Info); +collect_attribute(import, M, Info) -> + finfo_add_module_import(M, Info); +collect_attribute(file, _, Info) -> + Info; +collect_attribute(record, {R, L}, Info) -> + finfo_add_record(R, L, Info); +collect_attribute(spec, _, Info) -> + Info; +collect_attribute(_, {N, V}, Info) -> + finfo_add_attribute(N, V, Info). + +%% Abstract datatype for collecting module information. + +-record(forms, {module, exports, module_imports, imports, attributes, + records, errors, warnings, functions, rules}). + +new_finfo() -> + #forms{module = none, + exports = [], + module_imports = [], + imports = [], + attributes = [], + records = [], + errors = [], + warnings = [], + functions = [], + rules = [] + }. + +finfo_set_module(Name, Info) -> + case Info#forms.module of + none -> + Info#forms{module = {value, Name}}; + {value, _} -> + Info + end. + +finfo_add_exports(L, Info) -> + Info#forms{exports = L ++ Info#forms.exports}. + +finfo_add_module_import(M, Info) -> + Info#forms{module_imports = [M | Info#forms.module_imports]}. + +finfo_add_imports(M, L, Info) -> + Es = Info#forms.imports, + case lists:keyfind(M, 1, Es) of + {_, L1} -> + Es1 = lists:keyreplace(M, 1, Es, {M, L ++ L1}), + Info#forms{imports = Es1}; + false -> + Info#forms{imports = [{M, L} | Es]} + end. + +finfo_add_attribute(Name, Val, Info) -> + Info#forms{attributes = [{Name, Val} | Info#forms.attributes]}. + +finfo_add_record(R, L, Info) -> + Info#forms{records = [{R, L} | Info#forms.records]}. + +finfo_add_error(R, Info) -> + Info#forms{errors = [R | Info#forms.errors]}. + +finfo_add_warning(R, Info) -> + Info#forms{warnings = [R | Info#forms.warnings]}. + +finfo_add_function(F, Info) -> + Info#forms{functions = [F | Info#forms.functions]}. + +finfo_add_rule(F, Info) -> + Info#forms{rules = [F | Info#forms.rules]}. + +finfo_to_list(Info) -> + [{Key, Value} + || {Key, {value, Value}} <- + [{module, Info#forms.module}, + {exports, list_value(Info#forms.exports)}, + {imports, list_value(Info#forms.imports)}, + {module_imports, list_value(Info#forms.module_imports)}, + {attributes, list_value(Info#forms.attributes)}, + {records, list_value(Info#forms.records)}, + {errors, list_value(Info#forms.errors)}, + {warnings, list_value(Info#forms.warnings)}, + {functions, list_value(Info#forms.functions)}, + {rules, list_value(Info#forms.rules)} + ]]. + +list_value([]) -> + none; +list_value(List) -> + {value, List}. + + +%% ===================================================================== +%% @spec analyze_form(Node::syntaxTree()) -> {atom(), term()} | atom() +%% +%% @doc Analyzes a "source code form" node. If `Node' is a +%% "form" type (cf. `erl_syntax:is_form/1'), the returned +%% value is a tuple `{Type, Info}' where `Type' is +%% the node type and `Info' depends on `Type', as +%% follows: +%% <dl> +%% <dt>`{attribute, Info}'</dt> +%% +%% <dd>where `Info = analyze_attribute(Node)'.</dd> +%% +%% <dt>`{error_marker, Info}'</dt> +%% +%% <dd>where `Info = +%% erl_syntax:error_marker_info(Node)'.</dd> +%% +%% <dt>`{function, Info}'</dt> +%% +%% <dd>where `Info = analyze_function(Node)'.</dd> +%% +%% <dt>`{rule, Info}'</dt> +%% +%% <dd>where `Info = analyze_rule(Node)'.</dd> +%% +%% <dt>`{warning_marker, Info}'</dt> +%% +%% <dd>where `Info = +%% erl_syntax:warning_marker_info(Node)'.</dd> +%% </dl> +%% For other types of forms, only the node type is returned. +%% +%% The evaluation throws `syntax_error' if +%% `Node' is not well-formed. +%% +%% @see analyze_attribute/1 +%% @see analyze_function/1 +%% @see analyze_rule/1 +%% @see erl_syntax:is_form/1 +%% @see erl_syntax:error_marker_info/1 +%% @see erl_syntax:warning_marker_info/1 + +analyze_form(Node) -> + case erl_syntax:type(Node) of + attribute -> + {attribute, analyze_attribute(Node)}; + function -> + {function, analyze_function(Node)}; + rule -> + {rule, analyze_rule(Node)}; + error_marker -> + {error_marker, erl_syntax:error_marker_info(Node)}; + warning_marker -> + {warning_marker, erl_syntax:warning_marker_info(Node)}; + _ -> + case erl_syntax:is_form(Node) of + true -> + erl_syntax:type(Node); + false -> + throw(syntax_error) + end + end. + +%% ===================================================================== +%% @spec analyze_attribute(Node::syntaxTree()) -> +%% preprocessor | {atom(), atom()} +%% +%% @doc Analyzes an attribute node. If `Node' represents a +%% preprocessor directive, the atom `preprocessor' is +%% returned. Otherwise, if `Node' represents a module +%% attribute "`-<em>Name</em>...'", a tuple `{Name, +%% Info}' is returned, where `Info' depends on +%% `Name', as follows: +%% <dl> +%% <dt>`{module, Info}'</dt> +%% +%% <dd>where `Info = +%% analyze_module_attribute(Node)'.</dd> +%% +%% <dt>`{export, Info}'</dt> +%% +%% <dd>where `Info = +%% analyze_export_attribute(Node)'.</dd> +%% +%% <dt>`{import, Info}'</dt> +%% +%% <dd>where `Info = +%% analyze_import_attribute(Node)'.</dd> +%% +%% <dt>`{file, Info}'</dt> +%% +%% <dd>where `Info = +%% analyze_file_attribute(Node)'.</dd> +%% +%% <dt>`{record, Info}'</dt> +%% +%% <dd>where `Info = +%% analyze_record_attribute(Node)'.</dd> +%% +%% <dt>`{Name, Info}'</dt> +%% +%% <dd>where `{Name, Info} = +%% analyze_wild_attribute(Node)'.</dd> +%% </dl> +%% The evaluation throws `syntax_error' if `Node' +%% does not represent a well-formed module attribute. +%% +%% @see analyze_module_attribute/1 +%% @see analyze_export_attribute/1 +%% @see analyze_import_attribute/1 +%% @see analyze_file_attribute/1 +%% @see analyze_record_attribute/1 +%% @see analyze_wild_attribute/1 + +analyze_attribute(Node) -> + Name = erl_syntax:attribute_name(Node), + case erl_syntax:type(Name) of + atom -> + case erl_syntax:atom_value(Name) of + define -> preprocessor; + undef -> preprocessor; + include -> preprocessor; + include_lib -> preprocessor; + ifdef -> preprocessor; + ifndef -> preprocessor; + else -> preprocessor; + endif -> preprocessor; + A -> + {A, analyze_attribute(A, Node)} + end; + _ -> + throw(syntax_error) + end. + +analyze_attribute(module, Node) -> + analyze_module_attribute(Node); +analyze_attribute(export, Node) -> + analyze_export_attribute(Node); +analyze_attribute(import, Node) -> + analyze_import_attribute(Node); +analyze_attribute(file, Node) -> + analyze_file_attribute(Node); +analyze_attribute(record, Node) -> + analyze_record_attribute(Node); +analyze_attribute(define, _Node) -> + define; +analyze_attribute(spec, _Node) -> + spec; +analyze_attribute(_, Node) -> + %% A "wild" attribute (such as e.g. a `compile' directive). + analyze_wild_attribute(Node). + + +%% ===================================================================== +%% @spec analyze_module_attribute(Node::syntaxTree()) -> +%% Name::atom() | {Name::atom(), Variables::[atom()]} +%% +%% @doc Returns the module name and possible parameters declared by a +%% module attribute. If the attribute is a plain module declaration such +%% as `-module(name)', the result is the module name. If the attribute +%% is a parameterized module declaration, the result is a tuple +%% containing the module name and a list of the parameter variable +%% names. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed module +%% attribute. +%% +%% @see analyze_attribute/1 + +analyze_module_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + case erl_syntax:attribute_arguments(Node) of + [M] -> + module_name_to_atom(M); + [M, L] -> + M1 = module_name_to_atom(M), + L1 = analyze_variable_list(L), + {M1, L1}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + +analyze_variable_list(Node) -> + case erl_syntax:is_proper_list(Node) of + true -> + [erl_syntax:variable_name(V) + || V <- erl_syntax:list_elements(Node)]; + false -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_export_attribute(Node::syntaxTree()) -> [FunctionName] +%% +%% FunctionName = atom() | {atom(), integer()} +%% | {ModuleName, FunctionName} +%% ModuleName = atom() +%% +%% @doc Returns the list of function names declared by an export +%% attribute. We do not guarantee that each name occurs at most once in +%% the list. The order of listing is not defined. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed export +%% attribute. +%% +%% @see analyze_attribute/1 + +analyze_export_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + case erl_syntax:attribute_arguments(Node) of + [L] -> + analyze_function_name_list(L); + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + +analyze_function_name_list(Node) -> + case erl_syntax:is_proper_list(Node) of + true -> + [analyze_function_name(F) + || F <- erl_syntax:list_elements(Node)]; + false -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_function_name(Node::syntaxTree()) -> FunctionName +%% +%% FunctionName = atom() | {atom(), integer()} +%% | {ModuleName, FunctionName} +%% ModuleName = atom() +%% +%% @doc Returns the function name represented by a syntax tree. If +%% `Node' represents a function name, such as +%% "`foo/1'" or "`bloggs:fred/2'", a uniform +%% representation of that name is returned. Different nestings of arity +%% and module name qualifiers in the syntax tree does not affect the +%% result. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed function name. + +analyze_function_name(Node) -> + case erl_syntax:type(Node) of + atom -> + erl_syntax:atom_value(Node); + arity_qualifier -> + A = erl_syntax:arity_qualifier_argument(Node), + case erl_syntax:type(A) of + integer -> + F = erl_syntax:arity_qualifier_body(Node), + F1 = analyze_function_name(F), + append_arity(erl_syntax:integer_value(A), F1); + _ -> + throw(syntax_error) + end; + module_qualifier -> + M = erl_syntax:module_qualifier_argument(Node), + case erl_syntax:type(M) of + atom -> + F = erl_syntax:module_qualifier_body(Node), + F1 = analyze_function_name(F), + {erl_syntax:atom_value(M), F1}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + +append_arity(A, {Module, Name}) -> + {Module, append_arity(A, Name)}; +append_arity(A, Name) when is_atom(Name) -> + {Name, A}; +append_arity(A, A) -> + A; +append_arity(_A, Name) -> + Name. % quietly drop extra arity in case of conflict + + +%% ===================================================================== +%% @spec analyze_import_attribute(Node::syntaxTree()) -> +%% {atom(), [FunctionName]} | atom() +%% +%% FunctionName = atom() | {atom(), integer()} +%% | {ModuleName, FunctionName} +%% ModuleName = atom() +%% +%% @doc Returns the module name and (if present) list of function names +%% declared by an import attribute. The returned value is an atom +%% `Module' or a pair `{Module, Names}', where +%% `Names' is a list of function names declared as imported +%% from the module named by `Module'. We do not guarantee +%% that each name occurs at most once in `Names'. The order +%% of listing is not defined. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed import +%% attribute. +%% +%% @see analyze_attribute/1 + +analyze_import_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + case erl_syntax:attribute_arguments(Node) of + [M] -> + module_name_to_atom(M); + [M, L] -> + M1 = module_name_to_atom(M), + L1 = analyze_function_name_list(L), + {M1, L1}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_wild_attribute(Node::syntaxTree()) -> {atom(), term()} +%% +%% @doc Returns the name and value of a "wild" attribute. The result is +%% the pair `{Name, Value}', if `Node' represents +%% "`-Name(Value)'". +%% +%% Note that no checking is done whether `Name' is a +%% reserved attribute name such as `module' or +%% `export': it is assumed that the attribute is "wild". +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed wild +%% attribute. +%% +%% @see analyze_attribute/1 + +analyze_wild_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + N = erl_syntax:attribute_name(Node), + case erl_syntax:type(N) of + atom -> + case erl_syntax:attribute_arguments(Node) of + [V] -> + case catch {ok, erl_syntax:concrete(V)} of + {ok, Val} -> + {erl_syntax:atom_value(N), Val}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_record_attribute(Node::syntaxTree()) -> +%% {atom(), Fields} +%% +%% Fields = [{atom(), none | syntaxTree()}] +%% +%% @doc Returns the name and the list of fields of a record declaration +%% attribute. The result is a pair `{Name, Fields}', if +%% `Node' represents "`-record(Name, {...}).'", +%% where `Fields' is a list of pairs `{Label, +%% Default}' for each field "`Label'" or "`Label = +%% <em>Default</em>'" in the declaration, listed in left-to-right +%% order. If the field has no default-value declaration, the value for +%% `Default' will be the atom `none'. We do not +%% guarantee that each label occurs at most one in the list. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed record declaration +%% attribute. +%% +%% @see analyze_attribute/1 +%% @see analyze_record_field/1 + +analyze_record_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + case erl_syntax:attribute_arguments(Node) of + [R, T] -> + case erl_syntax:type(R) of + atom -> + Es = analyze_record_attribute_tuple(T), + {erl_syntax:atom_value(R), Es}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + +analyze_record_attribute_tuple(Node) -> + case erl_syntax:type(Node) of + tuple -> + [analyze_record_field(F) + || F <- erl_syntax:tuple_elements(Node)]; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_record_expr(Node::syntaxTree()) -> +%% {atom(), Info} | atom() +%% +%% Info = {atom(), [{atom(), Value}]} | {atom(), atom()} | atom() +%% Value = none | syntaxTree() +%% +%% @doc Returns the record name and field name/names of a record +%% expression. If `Node' has type `record_expr', +%% `record_index_expr' or `record_access', a pair +%% `{Type, Info}' is returned, otherwise an atom +%% `Type' is returned. `Type' is the node type of +%% `Node', and `Info' depends on +%% `Type', as follows: +%% <dl> +%% <dt>`record_expr':</dt> +%% <dd>`{atom(), [{atom(), Value}]}'</dd> +%% <dt>`record_access':</dt> +%% <dd>`{atom(), atom()} | atom()'</dd> +%% <dt>`record_index_expr':</dt> +%% <dd>`{atom(), atom()}'</dd> +%% </dl> +%% +%% For a `record_expr' node, `Info' represents +%% the record name and the list of descriptors for the involved fields, +%% listed in the order they appear. (See +%% `analyze_record_field/1' for details on the field +%% descriptors). For a `record_access' node, +%% `Info' represents the record name and the field name (or +%% if the record name is not included, only the field name; this is +%% allowed only in Mnemosyne-query syntax). For a +%% `record_index_expr' node, `Info' represents the +%% record name and the name field name. +%% +%% The evaluation throws `syntax_error' if +%% `Node' represents a record expression that is not +%% well-formed. +%% +%% @see analyze_record_attribute/1 +%% @see analyze_record_field/1 + +analyze_record_expr(Node) -> + case erl_syntax:type(Node) of + record_expr -> + A = erl_syntax:record_expr_type(Node), + case erl_syntax:type(A) of + atom -> + Fs = [analyze_record_field(F) + || F <- erl_syntax:record_expr_fields(Node)], + {record_expr, {erl_syntax:atom_value(A), Fs}}; + _ -> + throw(syntax_error) + end; + record_access -> + F = erl_syntax:record_access_field(Node), + case erl_syntax:type(F) of + atom -> + case erl_syntax:record_access_type(Node) of + none -> + {record_access, erl_syntax:atom_value(F)}; + A -> + case erl_syntax:type(A) of + atom -> + {record_access, + {erl_syntax:atom_value(A), + erl_syntax:atom_value(F)}}; + _ -> + throw(syntax_error) + end + end; + _ -> + throw(syntax_error) + end; + record_index_expr -> + F = erl_syntax:record_index_expr_field(Node), + case erl_syntax:type(F) of + atom -> + A = erl_syntax:record_index_expr_type(Node), + case erl_syntax:type(A) of + atom -> + {record_index_expr, + {erl_syntax:atom_value(A), + erl_syntax:atom_value(F)}}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end; + Type -> + Type + end. + +%% ===================================================================== +%% @spec analyze_record_field(Node::syntaxTree()) -> {atom(), Value} +%% +%% Value = none | syntaxTree() +%% +%% @doc Returns the label and value-expression of a record field +%% specifier. The result is a pair `{Label, Value}', if +%% `Node' represents "`Label = <em>Value</em>'" or +%% "`Label'", where in the first case, `Value' is +%% a syntax tree, and in the second case `Value' is +%% `none'. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed record field +%% specifier. +%% +%% @see analyze_record_attribute/1 +%% @see analyze_record_expr/1 + +analyze_record_field(Node) -> + case erl_syntax:type(Node) of + record_field -> + A = erl_syntax:record_field_name(Node), + case erl_syntax:type(A) of + atom -> + T = erl_syntax:record_field_value(Node), + {erl_syntax:atom_value(A), T}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_file_attribute(Node::syntaxTree()) -> +%% {string(), integer()} +%% +%% @doc Returns the file name and line number of a `file' +%% attribute. The result is the pair `{File, Line}' if +%% `Node' represents "`-file(File, Line).'". +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed `file' +%% attribute. +%% +%% @see analyze_attribute/1 + +analyze_file_attribute(Node) -> + case erl_syntax:type(Node) of + attribute -> + case erl_syntax:attribute_arguments(Node) of + [F, N] -> + case (erl_syntax:type(F) =:= string) + and (erl_syntax:type(N) =:= integer) of + true -> + {erl_syntax:string_value(F), + erl_syntax:integer_value(N)}; + false -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_function(Node::syntaxTree()) -> {atom(), integer()} +%% +%% @doc Returns the name and arity of a function definition. The result +%% is a pair `{Name, A}' if `Node' represents a +%% function definition "`Name(<em>P_1</em>, ..., <em>P_A</em>) -> +%% ...'". +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed function +%% definition. +%% +%% @see analyze_rule/1 + +analyze_function(Node) -> + case erl_syntax:type(Node) of + function -> + N = erl_syntax:function_name(Node), + case erl_syntax:type(N) of + atom -> + {erl_syntax:atom_value(N), + erl_syntax:function_arity(Node)}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_rule(Node::syntaxTree()) -> {atom(), integer()} +%% +%% @doc Returns the name and arity of a Mnemosyne rule. The result is a +%% pair `{Name, A}' if `Node' represents a rule +%% "`Name(<em>P_1</em>, ..., <em>P_A</em>) :- ...'". +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed Mnemosyne +%% rule. +%% +%% @see analyze_function/1 + +analyze_rule(Node) -> + case erl_syntax:type(Node) of + rule -> + N = erl_syntax:rule_name(Node), + case erl_syntax:type(N) of + atom -> + {erl_syntax:atom_value(N), + erl_syntax:rule_arity(Node)}; + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_implicit_fun(Node::syntaxTree()) -> FunctionName +%% +%% FunctionName = atom() | {atom(), integer()} +%% | {ModuleName, FunctionName} +%% ModuleName = atom() +%% +%% @doc Returns the name of an implicit fun expression "`fun +%% <em>F</em>'". The result is a representation of the function +%% name `F'. (Cf. `analyze_function_name/1'.) +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed implicit fun. +%% +%% @see analyze_function_name/1 + +analyze_implicit_fun(Node) -> + case erl_syntax:type(Node) of + implicit_fun -> + analyze_function_name( + erl_syntax:implicit_fun_name(Node)); + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec analyze_application(Node::syntaxTree()) -> FunctionName | Arity +%% +%% FunctionName = {atom(), Arity} +%% | {ModuleName, FunctionName} +%% Arity = integer() +%% ModuleName = atom() +%% +%% @doc Returns the name of a called function. The result is a +%% representation of the name of the applied function `F/A', +%% if `Node' represents a function application +%% "`<em>F</em>(<em>X_1</em>, ..., <em>X_A</em>)'". If the +%% function is not explicitly named (i.e., `F' is given by +%% some expression), only the arity `A' is returned. +%% +%% The evaluation throws `syntax_error' if +%% `Node' does not represent a well-formed application +%% expression. +%% +%% @see analyze_function_name/1 + +analyze_application(Node) -> + case erl_syntax:type(Node) of + application -> + A = length(erl_syntax:application_arguments(Node)), + F = erl_syntax:application_operator(Node), + case catch {ok, analyze_function_name(F)} of + syntax_error -> + A; + {ok, N} -> + append_arity(A, N); + _ -> + throw(syntax_error) + end; + _ -> + throw(syntax_error) + end. + + +%% ===================================================================== +%% @spec function_name_expansions(Names::[Name]) -> [{ShortName, Name}] +%% +%% Name = ShortName | {atom(), Name} +%% ShortName = atom() | {atom(), integer()} +%% +%% @doc Creates a mapping from corresponding short names to full +%% function names. Names are represented by nested tuples of atoms and +%% integers (cf. `analyze_function_name/1'). The result is a +%% list containing a pair `{ShortName, Name}' for each +%% element `Name' in the given list, where the corresponding +%% `ShortName' is the rightmost-innermost part of +%% `Name'. The list thus represents a finite mapping from +%% unqualified names to the corresponding qualified names. +%% +%% Note: the resulting list can contain more than one tuple +%% `{ShortName, Name}' for the same `ShortName', +%% possibly with different values for `Name', depending on +%% the given list. +%% +%% @see analyze_function_name/1 + +function_name_expansions(Fs) -> + function_name_expansions(Fs, []). + +function_name_expansions([F | Fs], Ack) -> + function_name_expansions(Fs, + function_name_expansions(F, F, Ack)); +function_name_expansions([], Ack) -> + Ack. + +function_name_expansions({A, N}, Name, Ack) when is_integer(N) -> + [{{A, N}, Name} | Ack]; +function_name_expansions({_, N}, Name, Ack) -> + function_name_expansions(N, Name, Ack); +function_name_expansions(A, Name, Ack) -> + [{A, Name} | Ack]. + + +%% ===================================================================== +%% @spec strip_comments(Tree::syntaxTree()) -> syntaxTree() +%% +%% @doc Removes all comments from all nodes of a syntax tree. All other +%% attributes (such as position information) remain unchanged. +%% Standalone comments in form lists are removed; any other standalone +%% comments are changed into null-comments (no text, no indentation). + +strip_comments(Tree) -> + map(fun strip_comments_1/1, Tree). + +strip_comments_1(T) -> + case erl_syntax:type(T) of + form_list -> + Es = erl_syntax:form_list_elements(T), + Es1 = [E || E <- Es, erl_syntax:type(E) /= comment], + T1 = erl_syntax:copy_attrs(T, erl_syntax:form_list(Es1)), + erl_syntax:remove_comments(T1); + comment -> + erl_syntax:comment([]); + _ -> + erl_syntax:remove_comments(T) + end. + +%% ===================================================================== +%% @spec to_comment(Tree) -> syntaxTree() +%% @equiv to_comment(Tree, "% ") + +to_comment(Tree) -> + to_comment(Tree, "% "). + +%% ===================================================================== +%% @spec to_comment(Tree::syntaxTree(), Prefix::string()) -> +%% syntaxTree() +%% +%% @doc Equivalent to `to_comment(Tree, Prefix, F)' for a +%% default formatting function `F'. The default +%% `F' simply calls `erl_prettypr:format/1'. +%% +%% @see to_comment/3 +%% @see erl_prettypr:format/1 + +to_comment(Tree, Prefix) -> + F = fun (T) -> erl_prettypr:format(T) end, + to_comment(Tree, Prefix, F). + +%% ===================================================================== +%% @spec to_comment(Tree::syntaxTree(), Prefix::string(), Printer) -> +%% syntaxTree() +%% +%% Printer = (syntaxTree()) -> string() +%% +%% @doc Transforms a syntax tree into an abstract comment. The lines of +%% the comment contain the text for `Node', as produced by +%% the given `Printer' function. Each line of the comment is +%% prefixed by the string `Prefix' (this does not include the +%% initial "`%'" character of the comment line). +%% +%% For example, the result of +%% `to_comment(erl_syntax:abstract([a,b,c]))' represents +%% <pre> +%% %% [a,b,c]</pre> +%% (cf. `to_comment/1'). +%% +%% Note: the text returned by the formatting function will be split +%% automatically into separate comment lines at each line break. No +%% extra work is needed. +%% +%% @see to_comment/1 +%% @see to_comment/2 + +to_comment(Tree, Prefix, F) -> + erl_syntax:comment(split_lines(F(Tree), Prefix)). + + +%% ===================================================================== +%% @spec limit(Tree, Depth) -> syntaxTree() +%% +%% @doc Equivalent to `limit(Tree, Depth, Text)' using the +%% text `"..."' as default replacement. +%% +%% @see limit/3 +%% @see erl_syntax:text/1 + +limit(Tree, Depth) -> + limit(Tree, Depth, erl_syntax:text("...")). + +%% ===================================================================== +%% @spec limit(Tree::syntaxTree(), Depth::integer(), +%% Node::syntaxTree()) -> syntaxTree() +%% +%% @doc Limits a syntax tree to a specified depth. Replaces all non-leaf +%% subtrees in `Tree' at the given `Depth' by +%% `Node'. If `Depth' is negative, the result is +%% always `Node', even if `Tree' has no subtrees. +%% +%% When a group of subtrees (as e.g., the argument list of an +%% `application' node) is at the specified depth, and there +%% are two or more subtrees in the group, these will be collectively +%% replaced by `Node' even if they are leaf nodes. Groups of +%% subtrees that are above the specified depth will be limited in size, +%% as if each subsequent tree in the group were one level deeper than +%% the previous. E.g., if `Tree' represents a list of +%% integers "`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'", the result +%% of `limit(Tree, 5)' will represent `[1, 2, 3, 4, +%% ...]'. +%% +%% The resulting syntax tree is typically only useful for +%% pretty-printing or similar visual formatting. +%% +%% @see limit/2 + +limit(_Tree, Depth, Node) when Depth < 0 -> + Node; +limit(Tree, Depth, Node) -> + limit_1(Tree, Depth, Node). + +limit_1(Tree, Depth, Node) -> + %% Depth is nonnegative here. + case erl_syntax:subtrees(Tree) of + [] -> + if Depth > 0 -> + Tree; + true -> + case is_simple_leaf(Tree) of + true -> + Tree; + false -> + Node + end + end; + Gs -> + if Depth > 1 -> + Gs1 = [[limit_1(T, Depth - 1, Node) + || T <- limit_list(G, Depth, Node)] + || G <- Gs], + rewrite(Tree, + erl_syntax:make_tree(erl_syntax:type(Tree), + Gs1)); + Depth =:= 0 -> + %% Depth is zero, and this is not a leaf node + %% so we always replace it. + Node; + true -> + %% Depth is 1, so all subtrees are to be cut. + %% This is done groupwise. + Gs1 = [cut_group(G, Node) || G <- Gs], + rewrite(Tree, + erl_syntax:make_tree(erl_syntax:type(Tree), + Gs1)) + end + end. + +cut_group([], _Node) -> + []; +cut_group([T], Node) -> + %% Only if the group contains a single subtree do we try to + %% preserve it if suitable. + [limit_1(T, 0, Node)]; +cut_group(_Ts, Node) -> + [Node]. + +is_simple_leaf(Tree) -> + case erl_syntax:type(Tree) of + atom -> true; + char -> true; + float -> true; + integer -> true; + nil -> true; + operator -> true; + tuple -> true; + underscore -> true; + variable -> true; + _ -> false + end. + +%% If list has more than N elements, take the N - 1 first and +%% append Node; otherwise return list as is. + +limit_list(Ts, N, Node) -> + if length(Ts) > N -> + limit_list_1(Ts, N - 1, Node); + true -> + Ts + end. + +limit_list_1([T | Ts], N, Node) -> + if N > 0 -> + [T | limit_list_1(Ts, N - 1, Node)]; + true -> + [Node] + end; +limit_list_1([], _N, _Node) -> + []. + + +%% ===================================================================== +%% Utility functions + +rewrite(Tree, Tree1) -> + erl_syntax:copy_attrs(Tree, Tree1). + +module_name_to_atom(M) -> + case erl_syntax:type(M) of + atom -> + erl_syntax:atom_value(M); + qualified_name -> + list_to_atom(packages:concat( + [erl_syntax:atom_value(A) + || A <- erl_syntax:qualified_name_segments(M)]) + ); + _ -> + throw(syntax_error) + end. + +%% This splits lines at line terminators and expands tab characters to +%% spaces. The width of a tab is assumed to be 8. + +% split_lines(Cs) -> +% split_lines(Cs, ""). + +split_lines(Cs, Prefix) -> + split_lines(Cs, Prefix, 0). + +split_lines(Cs, Prefix, N) -> + lists:reverse(split_lines(Cs, N, [], [], Prefix)). + +split_lines([$\r, $\n | Cs], _N, Cs1, Ls, Prefix) -> + split_lines_1(Cs, Cs1, Ls, Prefix); +split_lines([$\r | Cs], _N, Cs1, Ls, Prefix) -> + split_lines_1(Cs, Cs1, Ls, Prefix); +split_lines([$\n | Cs], _N, Cs1, Ls, Prefix) -> + split_lines_1(Cs, Cs1, Ls, Prefix); +split_lines([$\t | Cs], N, Cs1, Ls, Prefix) -> + split_lines(Cs, 0, push(8 - (N rem 8), $\040, Cs1), Ls, + Prefix); +split_lines([C | Cs], N, Cs1, Ls, Prefix) -> + split_lines(Cs, N + 1, [C | Cs1], Ls, Prefix); +split_lines([], _, [], Ls, _) -> + Ls; +split_lines([], _N, Cs, Ls, Prefix) -> + [Prefix ++ lists:reverse(Cs) | Ls]. + +split_lines_1(Cs, Cs1, Ls, Prefix) -> + split_lines(Cs, 0, [], [Prefix ++ lists:reverse(Cs1) | Ls], + Prefix). + +push(N, C, Cs) when N > 0 -> + push(N - 1, C, [C | Cs]); +push(0, _, Cs) -> + Cs. + |