%%% Copyright (C) 2013 Tomas Abrahamsson %%% %%% Author: Tomas Abrahamsson %%% %%% 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.1 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., 51 Franklin Street, Fifth Floor, Boston, %%% MA 02110-1301 USA %% @doc %% This module is only used internally within the `gpb'. %% You do not need to use it to compile a protobuf file. You will %% use it indirectly though, since the protobuf compiler uses it heavily. %% Thus, this documentation is mostly for internal use. %% %% This module implements a parse transform, to create syntax trees, %% together with runtime support for subsequent transformation of %% those syntax trees. %% %% Include the file `gpb_codegen.hrl' or specify %% `-compile({parse_transform,gpb_codegen}).' %% to activate this parse transform. %% %% The syntax tree operations below are provided. An `stree()' is a %% syntax tree. Use for example `?expr(...)' or `?case_clause(...)' to %% create syntax trees. %% %%
%%
`gpb_codegen:mk_fn(FnName, fun(Arg ...) -> Body end) -> stree()'
%%
Will be replaced by a parse-tree for a function `FnName', %% with `Arg's and `Body' as in the specified fun. %% The `FnName' is evaluated at run-time, not at compile-time. %%
%%
`gpb_codegen:mk_fn(FnName, Fun, RtTransforms) -> stree()'
%%

Like `gpb_codegen:mk_fn/2', but apply `RtTransforms' at run-time %% before returning the syntax tree.

%%

Inside the `Fun', a call to `call_self' is treated specially %% as a recursive call back to the function. NB: It is implemented %% as a simple term replacement, see below, so any occurrences %% of the atom `call_self' will be replaced regardless of whether %% it is in a function call or not.

%%

The `RtTransforms' are applied in the order specified.

%%

The following `RtTransforms' are available:

%%
%%
`{replace_term, Marker::atom(), Replacement::term()}'
%%
Replace any occurrences of `Marker' with the syntax tree %% representing `Replacement', which must be something that could %% have occurred as a literal term in some program text, %% thus it must not contain any funs, pids, ports, references or such. %%
%%
`{replace_tree, Marker::atom(), Replacement::stree()}'
%%
Replace any occurrences of `Marker' with the syntax tree %% `Replacement'. %%
%%
`{splice_trees, Marker::atom(), Replacements::[stree()]}'
%%
For any list that contains `Marker', insert the `Replacements' %% syntax trees instead of the `Marker'. Such lists are for example %% lists of arguments for a function, lists of elements in a tuple %% and lists of expressions in a function body, but not necessarily %% elements in literal list term, since these may be represented %% as cons elements in the syntax tree. %%
%%
`{splice_clauses, Marker::atom(), Replacements::[stree()]}'
%%
For case clauses (and function clauses), where the pattern is a %% single atom, `Marker', insert the case clauses in `Replacements' %% instead. %% Use the `?case_clause/1' macro to create a syntax tree %% for a case clause. %%
%%
`{repeat_clauses, Marker::atom, Rep::[[transform()]]}'
%%

Repeat template clauses zero or more times: as many times %% as `length(Rep)'. For each repetition, apply the %% list of transformations to the clause, be it a function clause, %% case clause etc.

Example: a transformation

%%
%%                gpb_codegen:format_fn(
%%                   SomeName,
%%                   fun(s)     -> v;
%%                      (Other) -> erlang:error({not_found, Other})
%%                   end,
%%                   [{repeat_clauses, s,
%%                     [[{replace_term, s, Sym}, {replace_term, v, Value}]
%%                      || {Sym, Value} <- Mapping]}])
%%             
%%
%%
%%
%%
`gpb_codegen:format_fn(FnName, Fun [, RtTransforms]) -> iolist()'
%%
like `gpb_codegen:mk_fn/2,3', but format the result into %% an iolist by calling `erl_prettypr:format'. The resulting %% iolist ends with a newline.
%%
`?expr(Expr)' or %% `gpb_codegen:expr(Expr)'
%%
Will be replaced by the syntax tree for a `Expr'.
%%
`?expr(Expr, RtTransforms)' or %% `gpb_codegen:expr(Expr, RtTransforms)'
%%
Like gpb_codegen:expr/1, but apply `RtTransforms' at run-time.
%%
`?exprs(Expr, ..., RtTransforms)' or %% `gpb_codegen:expr(Expr, ..., RtTransforms)'
%%
Like gpb_codegen:expr/1, but create a list of expressions. %% The last parameter must always be a list of run-time transforms. %% The macro form has support only up to some number of params.
%%
`?case_clause(Pattern [when Guard] -> Body)' or %% `gpb_codegen:case_clause(CaseExpression)'
%%

Will be replaced with the syntax tree for the case clause. %% Only one case clause, the first, is considered. %% When invoked using the `gpb_codegen:case_clause/1' function, %% a complete `case Expr of Clause end' must be provided; %% the `Expr' is ignored.

%%

Examples: `?case_clause(1 -> 2)' or `?case_clause(_ -> other)' or %% `gpb_codegen:case_clause(case dummy of 1 -> 2 end)'.

%%

In the macro form, some limitations apply:

%%
    %%
  • It is only possible to specify one `Guard'; %% it is _not_ possible to write for example: %% `?case_clause(L when is_list(L), length(L) > 2 -> x)' %% This is because the preprocessor will interpret %% it as two macro arguments, delimited by the comma %% in the middle between the two guards. %% This limitation does not apply when using the %% `gpb_codegen:case_clause/1' approach.
  • %%
  • It is only possible to specify one `Body' expression, %% because of the same preprocessor intermingling, but it %% is possible to work around this using `begin' ... `end'. %% This limitation does not apply when using the %% `gpb_codegen:case_clause/1' approach.
  • %%
%%
%%
`?case_clause(Pattern [when Guard] -> Body, RtTransforms)' or %% `gpb_codegen:case_clause(CaseExpression, RtTransforms)'
%%
Like `?case_clause/1' or `gpb_codegen:case_clause/1' %% but apply the RtTransforms to the syntax tree. %%
%%
`?if_clause(Guard -> Body[, RtTransforms])' or %% `gpb_codegen:if_clause(IfExpression[, RtTransforms])'
%%
Like `?case_clause/1,2' but for if-clauses.
%%
`?fn_clause(fun(...) -> ... end, [, RtTransforms])' or %% `gpb_codegen:fn_clause(FunExpression[, RtTransforms])'
%%
Like `?case_clause/1,2' but for function clauses.
%%
`?receive_clause(Pattern -> Body, [, RtTransforms])' or %% `gpb_codegen:receive_clause(receive ... -> ... end[, RtTransforms])' %%
%%
Like `?case_clause/1,2' but for receive clauses.
%%
%% %% Note that there is also a generation-time dependency (ie at %% run-time for the code-generating code) to this module. %% The generated code has no dependency to this module, though. %% @end -module(gpb_codegen). -export([parse_transform/2]). -export([runtime_fn_transform/2, runtime_fn_transform/3]). -export([runtime_expr_transform/1, runtime_expr_transform/2]). -export([runtime_exprs_transform/2]). -export([erl_prettypr_format_nl/1]). %% Exported just to be able to give a (more informative) error than undef -export([mk_fn/2, mk_fn/3, format_fn/2, format_fn/3]). -export([expr/1, expr/2]). -export([exprs/2, exprs/3, exprs/4, exprs/5, exprs/6]). %% as many as in .hrl -export([case_clause/1, case_clause/2]). -export([fn_clause/1, fn_clause/2]). -export([if_clause/1, if_clause/2]). -export([receive_clause/1, receive_clause/2]). -define(ff(Fmt, Args), lists:flatten(io_lib:format(Fmt, Args))). %%@hidden parse_transform(Forms, Opts) -> with_increased_backtrace_depth( fun() -> transform_forms(Forms, Opts) end). %%@hidden -spec mk_fn(atom(), fun((...) -> term())) -> no_return(). mk_fn(Name, Fun) -> error_invalid_call(mk_fn, [Name, Fun]). %%@hidden -spec mk_fn(atom(), fun((...) -> term()), list()) -> no_return(). mk_fn(Name, Fun, Ts) -> error_invalid_call(mk_fn, [Name, Fun, Ts]). %%@hidden -spec format_fn(atom(), fun((...) -> term())) -> no_return(). format_fn(Name, Fun) -> error_invalid_call(format_fn, [Name, Fun]). %%@hidden -spec format_fn(atom(), fun((...) -> term()), list()) -> no_return(). format_fn(Name, Fun, Ts) -> error_invalid_call(format_fn, [Name, Fun, Ts]). %%@hidden -spec expr(term()) -> no_return(). expr(E) -> error_invalid_call(expr, [E]). %%@hidden -spec expr(term(), list()) -> no_return(). expr(E, Ts) -> error_invalid_call(expr, [E, Ts]). %%@hidden -spec exprs(term(), list()) -> no_return(). exprs(E1, Ts) -> error_invalid_call(exprs, [E1, Ts]). %%@hidden -spec exprs(term(), term(), list()) -> no_return(). exprs(E1, E2, Ts) -> error_invalid_call(exprs, [E1, E2, Ts]). %%@hidden -spec exprs(term(), term(), term(), list()) -> no_return(). exprs(E1, E2, E3, Ts) -> error_invalid_call(exprs, [E1, E2, E3, Ts]). %%@hidden -spec exprs(term(), term(), term(), term(), list()) -> no_return(). exprs(E1, E2, E3, E4, Ts) -> error_invalid_call(exprs, [E1, E2, E3, E4, Ts]). %%@hidden -spec exprs(term(), term(), term(), term(), term(), list()) -> no_return(). exprs(E1, E2, E3, E4, E5, Ts) -> error_invalid_call(exprs, [E1, E2, E3, E4, E5, Ts]). %%@hidden -spec case_clause(term()) -> no_return(). case_clause(CC) -> error_invalid_call(case_clause, [CC]). %%@hidden -spec case_clause(term(), list()) -> no_return(). case_clause(CC, Ts) -> error_invalid_call(case_clause, [CC, Ts]). %%@hidden -spec fn_clause(term()) -> no_return(). fn_clause(FC) -> error_invalid_call(fn_clause, [FC]). %%@hidden -spec fn_clause(term(), list()) -> no_return(). fn_clause(FC, Ts) -> error_invalid_call(fn_clause, [FC, Ts]). %%@hidden -spec if_clause(term()) -> no_return(). if_clause(FC) -> error_invalid_call(if_clause, [FC]). %%@hidden -spec if_clause(term(), list()) -> no_return(). if_clause(FC, Ts) -> error_invalid_call(if_clause, [FC, Ts]). %%@hidden -spec receive_clause(term()) -> no_return(). receive_clause(FC) -> error_invalid_call(receive_clause, [FC]). %%@hidden -spec receive_clause(term(), list()) -> no_return(). receive_clause(FC, Ts) -> error_invalid_call(receive_clause, [FC, Ts]). error_invalid_call(Fn, Args) -> erlang:error({badcall, {{?MODULE, Fn, Args}, ["should be transformed with parse transform, " "not called directly"]}}). transform_forms(Forms, Opts) -> Mapper = mk_transform_fn(Forms, Opts), [debug_form(erl_syntax:revert(transform_form(Mapper, Form)), Opts) || Form <- Forms]. debug_form(NewForm, Opts) -> case debug_form_generation_p(Opts) of true -> try io:format("~s~n", [erl_prettypr:format(NewForm)]) catch _:_ -> io:format("Non-pretty-printable:~n ~p", [NewForm]) end, NewForm; false -> NewForm end. debug_form_generation_p(Opts) -> proplists:get_bool(debug_pt, proplists:unfold(Opts)). mk_transform_fn(Forms, Opts) -> TOpts = maybe_opts_for_reversion_of_local_implicit_funs_bug() ++ Opts, fun(Node) -> Type = erl_syntax:type(Node), transform_node(Type, Node, Forms, TOpts) end. transform_form(Mapper, Form) -> erl_syntax_lib:map(Mapper, Form). transform_node(application, Node, AllForms, _Opts) -> %% General idea here: transform a "call" to %% %% gpb_codegen:mk_fn(Name, Def, RtTransforms) %% %% into a generation-time call to: %% %% ?MODULE:runtime_fn_transform(Name, ParseTreeForDef, RtTransforms) %% %% The Def can be either of the forms: %% - fun(...) -> ... end %% - fun somename/X (for some arity X) %% - (but not module:somename/X %% since we need the parse tree for the function somename/X) %% case erl_syntax_lib:analyze_application(Node) of {?MODULE, {mk_fn, 2}} -> [FnNameExpr, DefAsFun] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), mk_runtime_fn_transform_revert_invoker(FnNameExpr, FnClauses, []); {?MODULE, {mk_fn, 3}} -> [FnNameExpr, DefAsFun, RtTransforms] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), mk_runtime_fn_transform_revert_invoker(FnNameExpr, FnClauses, [RtTransforms]); {?MODULE, {format_fn, 2}} -> [FnNameExpr, DefAsFun] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), mk_runtime_fn_transform_format_invoker(FnNameExpr, FnClauses, []); {?MODULE, {format_fn, 3}} -> [FnNameExpr, DefAsFun, RtTransforms] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), mk_runtime_fn_transform_format_invoker(FnNameExpr, FnClauses, [RtTransforms]); {?MODULE, {expr, 1}} -> [Expr] = erl_syntax:application_arguments(Node), erl_syntax:abstract(Expr); {?MODULE, {expr, 2}} -> [Expr, RtTransforms] = erl_syntax:application_arguments(Node), mk_apply(?MODULE, runtime_expr_transform, [erl_syntax:abstract(Expr), RtTransforms]); {?MODULE, {exprs, Arity}} when Arity >= 2 -> ExprsAndRtTransforms = erl_syntax:application_arguments(Node), {Exprs, RtTransforms} = split_out_last(ExprsAndRtTransforms), mk_apply(?MODULE, runtime_exprs_transform, [erl_syntax:abstract(Exprs), RtTransforms]); {?MODULE, {case_clause, 1}} -> [Expr] = erl_syntax:application_arguments(Node), case_expr_to_parse_tree_for_clause(Expr, []); {?MODULE, {case_clause, 2}} -> [Expr, RtTransforms] = erl_syntax:application_arguments(Node), case_expr_to_parse_tree_for_clause(Expr, [RtTransforms]); {?MODULE, {fn_clause, 1}} -> [DefAsFun] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), fun_to_parse_tree_for_clause(FnClauses, []); {?MODULE, {fn_clause, 2}} -> [DefAsFun, RtTransforms] = erl_syntax:application_arguments(Node), FnClauses = find_fun_form_clauses(DefAsFun, AllForms), fun_to_parse_tree_for_clause(FnClauses, [RtTransforms]); {?MODULE, {if_clause, 1}} -> [Expr] = erl_syntax:application_arguments(Node), if_to_parse_tree_for_clause(Expr, []); {?MODULE, {if_clause, 2}} -> [Expr, RtTransforms] = erl_syntax:application_arguments(Node), if_to_parse_tree_for_clause(Expr, [RtTransforms]); {?MODULE, {receive_clause, 1}} -> [Expr] = erl_syntax:application_arguments(Node), receive_to_parse_tree_for_clause(Expr, []); {?MODULE, {receive_clause, 2}} -> [Expr, RtTransforms] = erl_syntax:application_arguments(Node), receive_to_parse_tree_for_clause(Expr, [RtTransforms]); _X -> Node end; transform_node(implicit_fun, Node, _Forms, Opts) -> %% In R16B03, there's an unfortunate bug in erl_syntax for reverting %% exprs on the form "fun some_function/17", aka local implicit funs. %% I've found that a work around for the bug is to have something %% that's already on erl_parse format, so create local implicit funs %% on the erl_parse format. case proplists:get_bool(implicit_fun_revert_bug_r16b03, Opts) of true -> %% Create something that's already in erl_parse format case analyze_implicit_fun_name(Node) of {FnName, Arity} when is_atom(FnName), is_integer(Arity) -> Pos = erl_syntax:get_pos(Node), {'fun', Pos, {function, FnName, Arity}}; _ -> %% No bug for other type of implicit funs, e.g. "fun m:f/2" Node end; false -> %% No bug workaround needed Node end; transform_node(_Type, Node, _Forms, _Opts) -> Node. split_out_last(List) -> [Last | RRest] = lists:reverse(List), {lists:reverse(RRest), Last}. find_fun_form_clauses(DefAsFun, AllForms) -> case erl_syntax:type(DefAsFun) of fun_expr -> erl_syntax:fun_expr_clauses(DefAsFun); implicit_fun -> case analyze_implicit_fun_name(DefAsFun) of {DFnName, Arity} when is_integer(Arity) -> find_function_clauses(AllForms, DFnName, Arity); {Module, {FnName, Arity}} -> erlang:error({?MODULE,not_supported,mk_fn,remote_fn, ?ff("~p:~p/~w", [Module, FnName, Arity])}) end end. find_function_clauses([Form | Rest], FnName, Arity) -> case erl_syntax:type(Form) of function -> case analyze_function_name(Form) of {FnName, Arity} -> erl_syntax:function_clauses(Form); _X -> find_function_clauses(Rest, FnName, Arity) end; _ -> find_function_clauses(Rest, FnName, Arity) end; find_function_clauses([], FnName, Arity) -> erlang:error({reference_to_undefined_function,FnName,Arity}). mk_runtime_fn_transform_revert_invoker(FnNameExpr, FnClauses, RtTransforms) -> DummyFnName = erl_syntax:atom(fn_name_to_be_replaced_at_runtime), mk_apply(erl_syntax, revert, [mk_apply(?MODULE, runtime_fn_transform, [FnNameExpr, erl_syntax:abstract(erl_syntax:function(DummyFnName, FnClauses)) | RtTransforms])]). mk_runtime_fn_transform_format_invoker(FnNameExpr, FnClauses, RtTransforms) -> DummyFnName = erl_syntax:atom(fn_name_to_be_replaced_at_runtime), mk_apply(?MODULE, erl_prettypr_format_nl, [mk_apply(?MODULE, runtime_fn_transform, [FnNameExpr, erl_parse:abstract(erl_syntax:function(DummyFnName, FnClauses)) | RtTransforms])]). mk_apply(M, F, Args) when is_atom(M), is_atom(F) -> erl_syntax:application(erl_syntax:atom(M), erl_syntax:atom(F), Args). case_expr_to_parse_tree_for_clause(Expr, RtTransforms) -> case erl_syntax:type(Expr) of case_expr -> [Clause | _] = erl_syntax:case_expr_clauses(Expr), AbsSyntaxTree = erl_parse:abstract(erl_syntax:revert(Clause)), mk_apply(?MODULE, runtime_expr_transform, [AbsSyntaxTree | RtTransforms]); _OtherType -> Expr end. fun_to_parse_tree_for_clause([FnClause | _], RtTransforms) -> AbsSyntaxTree = erl_parse:abstract(erl_syntax:revert(FnClause)), mk_apply(?MODULE, runtime_expr_transform, [AbsSyntaxTree | RtTransforms]). if_to_parse_tree_for_clause(Expr, RtTransforms) -> case erl_syntax:type(Expr) of if_expr -> [Clause | _] = erl_syntax:if_expr_clauses(Expr), AbsSyntaxTree = erl_parse:abstract(erl_syntax:revert(Clause)), mk_apply(?MODULE, runtime_expr_transform, [AbsSyntaxTree | RtTransforms]); _OtherType -> Expr end. receive_to_parse_tree_for_clause(Expr, RtTransforms) -> case erl_syntax:type(Expr) of receive_expr -> [Clause | _] = erl_syntax:receive_expr_clauses(Expr), AbsSyntaxTree = erl_parse:abstract(erl_syntax:revert(Clause)), mk_apply(?MODULE, runtime_expr_transform, [AbsSyntaxTree | RtTransforms]); _OtherType -> Expr end. %% Main entry points at runtime. %%@hidden erl_prettypr_format_nl(Form) -> with_increased_backtrace_depth( fun() -> [erl_prettypr:format(Form), "\n\n"] end). %%@hidden runtime_fn_transform(FnName, FnParseTree) -> runtime_fn_transform(FnName, FnParseTree, []). %%@hidden runtime_fn_transform(FnName, FnParseTree, Transforms) -> with_increased_backtrace_depth( fun() -> Clauses = erl_syntax:function_clauses(FnParseTree), apply_transforms( erl_syntax:function(erl_syntax:atom(FnName), Clauses), Transforms ++ [{replace_term, call_self, FnName}]) end). %%@hidden runtime_expr_transform(ExprParseTree) -> runtime_expr_transform(ExprParseTree, []). %%@hidden runtime_expr_transform(ExprParseTree, Transforms) -> with_increased_backtrace_depth( fun() -> erl_syntax:copy_pos( ExprParseTree, apply_transforms(ExprParseTree, Transforms)) end). %%@hidden runtime_exprs_transform(ExprParseTrees, Transforms) -> with_increased_backtrace_depth( fun() -> %% To be able to apply the splice_trees also on the %% top-level, transform this into a single expr: %% begin ... end. Such an expr is called a block. Block1 = erl_syntax:block_expr(ExprParseTrees), Block2 = apply_transforms(Block1, Transforms), erl_syntax:block_expr_body(Block2) end). apply_transforms(ParseTree, Transforms) -> lists:foldl(fun apply_transform/2, ParseTree, Transforms). apply_transform({replace_term, Marker, Replacement}, ParseTree) -> erl_syntax_lib:map(term_replacing_mapper(Marker, Replacement), ParseTree); apply_transform({replace_tree, Marker, Replacement}, ParseTree) -> erl_syntax_lib:map(tree_replacing_mapper(Marker, Replacement), ParseTree); apply_transform({splice_trees, Marker, Replacements}, ParseTree) -> splice_trees(Marker, Replacements, ParseTree); apply_transform({splice_clauses, Marker, Replacements}, ParseTree) -> splice_clauses(Marker, Replacements, ParseTree); apply_transform({repeat_clauses, Marker, Repetitions}, ParseTree) -> repeat_clauses(Marker, Repetitions, ParseTree). term_replacing_mapper(Marker, Replacement) -> ReplacementTree = erl_parse:abstract(Replacement), tree_replacing_mapper(Marker, ReplacementTree). tree_replacing_mapper(Marker, Replacement) -> fun(Node) -> case analyze_atom_as_value(Node) of {atom, Marker} -> Replacement; {atom, _Other} -> Node; non_atom -> Node end end. splice_trees(Marker, Replacements, Tree) -> case erl_syntax:subtrees(Tree) of [] -> Tree; Gs -> F = fun(SubTree) -> splice_trees(Marker, Replacements, SubTree) end, Gs1 = [case split_list_on_marker(G, Marker) of marker_not_found -> [F(T) || T <- G]; {BeforeMarker, _MarkerTree, AfterMarker} -> Before = [F(T) || T <- BeforeMarker], After = [F(T) || T <- AfterMarker], Before ++ Replacements ++ After end || G <- Gs], Tree1 = erl_syntax:make_tree(erl_syntax:type(Tree), Gs1), erl_syntax:copy_attrs(Tree, Tree1) end. split_list_on_marker(Elems, Marker) -> split_aux(Elems, Marker, []). split_aux([X | Rest], Marker, Acc) -> case erl_syntax:type(X) of binary_field -> %% The marker (an atom) as a binary_field, will show up %% as a subtree of the subtree of the binary field, but %% must be replaced one level above that, so catch it here. case analyze_binary_field_body_as_atom_as_value(X) of {atom, Marker} -> {lists:reverse(Acc), X, Rest}; {atom, _Other} -> split_aux(Rest, Marker, [X | Acc]); non_atom -> split_aux(Rest, Marker, [X | Acc]) end; _OtherType -> case analyze_atom_as_value(X) of {atom, Marker} -> {lists:reverse(Acc), X, Rest}; {atom, _Other} -> split_aux(Rest, Marker, [X | Acc]); non_atom -> split_aux(Rest, Marker, [X | Acc]) end end; split_aux([], _Marker, _Acc) -> marker_not_found. splice_clauses(CMarker, Replacements, Tree) -> transform_clauses( CMarker, fun(_MarkerClause, _ClauseType) -> Replacements end, Tree). repeat_clauses(CMarker, Repetitions, Tree) -> transform_clauses( CMarker, fun(TemplateClause, _Type) -> [apply_transforms(TemplateClause, Transforms) || Transforms <- Repetitions] end, Tree). transform_clauses(CMarker, CTransformer, Tree) -> erl_syntax_lib:map( fun(Node) -> case erl_syntax:type(Node) of case_expr -> Arg = erl_syntax:case_expr_argument(Node), Cs = erl_syntax:case_expr_clauses(Node), case split_clauses_on_marker(Cs, CMarker, 'case') of marker_not_found -> Node; {Before, MarkerClause, After} -> New = CTransformer(MarkerClause, 'case'), Cs1 = Before ++ New ++ After, erl_syntax:case_expr(Arg, Cs1) end; fun_expr -> Cs = erl_syntax:fun_expr_clauses(Node), case split_clauses_on_marker(Cs, CMarker, 'fun') of marker_not_found -> Node; {Before, MarkerClause, After} -> New = CTransformer(MarkerClause, 'fun'), Cs1 = Before ++ New ++ After, erl_syntax:fun_expr(Cs1) end; function -> FnName = erl_syntax:function_name(Node), Cs = erl_syntax:function_clauses(Node), case split_clauses_on_marker(Cs, CMarker, function) of marker_not_found -> Node; {Before, MarkerClause, After} -> New = CTransformer(MarkerClause, function), Cs1 = Before ++ New ++ After, erl_syntax:function(FnName, Cs1) end; if_expr -> Cs = erl_syntax:if_expr_clauses(Node), case split_clauses_on_marker(Cs, CMarker, 'if') of marker_not_found -> Node; {Before, MarkerClause, After} -> New = CTransformer(MarkerClause, 'if'), Cs1 = Before ++ New ++ After, erl_syntax:if_expr(Cs1) end; receive_expr -> Cs = erl_syntax:receive_expr_clauses(Node), Tmo = erl_syntax:receive_expr_timeout(Node), Action = erl_syntax:receive_expr_action(Node), %% after case split_clauses_on_marker(Cs, CMarker, 'if') of marker_not_found -> Node; {Before, MarkerClause, After} -> New = CTransformer(MarkerClause, 'receive'), Cs1 = Before ++ New ++ After, erl_syntax:receive_expr(Cs1, Tmo, Action) end; _Other -> Node end end, Tree). split_clauses_on_marker(Clauses, CMarker, Type) -> csplit_aux(Clauses, CMarker, Type, []). csplit_aux([C | Rest], CMarker, Type, Acc) -> case erl_syntax:clause_patterns(C) of [CPattern | _] -> %% case clause or function clause case analyze_atom_as_value(CPattern) of {atom, CMarker} -> {lists:reverse(Acc), C, Rest}; {atom, _Other} -> csplit_aux(Rest, CMarker, Type, [C | Acc]); non_atom -> csplit_aux(Rest, CMarker, Type, [C | Acc]) end; [] when Type == 'if' -> %% an if-clause G = erl_syntax:clause_guard(C), case analyze_guard_as_atom_as_value(G) of {atom, CMarker} -> {lists:reverse(Acc), C, Rest}; {atom, _Other} -> csplit_aux(Rest, CMarker, Type, [C | Acc]); non_atom_guard -> csplit_aux(Rest, CMarker, Type, [C | Acc]) end; _CPatterns -> csplit_aux(Rest, CMarker, Type, [C | Acc]) end; csplit_aux([], _CMarker, _Type, _Acc) -> marker_not_found. analyze_guard_as_atom_as_value(G) -> %% The guard 'x' (the single atom x) is a disjunction of conjunctions: case erl_syntax:type(G) of disjunction -> [D1 | _] = erl_syntax:disjunction_body(G), case erl_syntax:type(D1) of conjunction -> [C1 | _] = erl_syntax:conjunction_body(D1), case analyze_atom_as_value(C1) of {atom, V} -> {atom, V}; non_atom -> non_atom_guard end; _ -> non_atom_guard end; _ -> non_atom_guard end. analyze_binary_field_body_as_atom_as_value(BinField) -> analyze_atom_as_value(erl_syntax:binary_field_body(BinField)). %% -> {Name,Arity} | {Module,{Name,Arity}} analyze_implicit_fun_name(Tree) -> erl_syntax_lib:analyze_function_name(erl_syntax:implicit_fun_name(Tree)). analyze_function_name(Tree) -> Name = erl_syntax_lib:analyze_function_name(erl_syntax:function_name(Tree)), Arity = erl_syntax:function_arity(Tree), %% Return a format like that of analyze_implicit_fun_name (no module) {Name, Arity}. %% -> {atom, atom()} | non_atom analyze_atom_as_value(Node) -> case erl_syntax:type(Node) of atom -> {atom, erl_syntax:atom_value(Node)}; _ -> non_atom end. with_increased_backtrace_depth(Fun) -> %% The backtrace_depth is quite often too short, %% when things go wrong inside the parse transform, %% or during the runtime application of additional transforms. %% %% The backtrace_depth controls how many levels of stack %% to include in the crash, too few levels means we only %% see the innermost function calls, not the originating %% top-level calls, making it difficult to debug errors. %% %% So: up it (temporarily). %% %% It is 8 in current Erlang/OTPs, but take some precautions %% in case it gets increased or changed in future versions. New = 32, try erlang:system_flag(backtrace_depth, New) of Old when Old < New -> try Fun() after erlang:system_flag(backtrace_depth, Old) end; Old when Old == New -> Fun(); Old when Old > New -> %% Don't decrease it! erlang:system_flag(backtrace_depth, Old), Fun() catch error:badarg -> %% Not available Fun() end. maybe_opts_for_reversion_of_local_implicit_funs_bug() -> {ok, Tokens, _End} = erl_scan:string("fun x/17."), {ok, [ImplicitFunExpr1]} = erl_parse:parse_exprs(Tokens), ImplicitFunExpr2 = erl_syntax:revert( erl_syntax:copy_pos( ImplicitFunExpr1, erl_syntax:implicit_fun( erl_syntax:copy_pos(ImplicitFunExpr1, erl_syntax:atom(x)), erl_syntax:copy_pos(ImplicitFunExpr1, erl_syntax:integer(17))))), case {ImplicitFunExpr1, ImplicitFunExpr2} of {Same, Same} -> %% No bug if the erl_parse format is the same %% as that from erl_syntax:revert, no bug-workaround options needed []; {{'fun', _, {function, x, 17}}, {'fun', _, {function, {atom, _, x}, {integer, _, 17}}}} -> %% The erl_parse format and the erl_syntax format are not the same! %% Found the bug when reverting local implicit funs in r16b03 [implicit_fun_revert_bug_r16b03] end.