Packages
gpb
4.12.0
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_codegen.erl
%%% Copyright (C) 2013 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% 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.
%%
%% <dl>
%% <dt>`gpb_codegen:mk_fn(FnName, fun(Arg ...) -> Body end) -> stree()'</dt>
%% <dd>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.
%% </dd>
%% <dt>`gpb_codegen:mk_fn(FnName, Fun, RtTransforms) -> stree()'</dt>
%% <dd><p>Like `gpb_codegen:mk_fn/2', but apply `RtTransforms' at run-time
%% before returning the syntax tree.</p>
%% <p>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.</p>
%% <p>The `RtTransforms' are applied in the order specified.</p>
%% <p>The following `RtTransforms' are available:</p>
%% <dl>
%% <dt>`{replace_term, Marker::atom(), Replacement::term()}'</dt>
%% <dd>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.
%% </dd>
%% <dt>`{replace_tree, Marker::atom(), Replacement::stree()}'</dt>
%% <dd>Replace any occurrences of `Marker' with the syntax tree
%% `Replacement'.
%% </dd>
%% <dt>`{splice_trees, Marker::atom(), Replacements::[stree()]}'</dt>
%% <dd>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.
%% </dd>
%% <dt>`{splice_clauses, Marker::atom(), Replacements::[stree()]}'</dt>
%% <dd>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.
%% </dd>
%% <dt>`{repeat_clauses, Marker::atom, Rep::[[transform()]]}'</dt>
%% <dd><p>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.</p><p>Example: a transformation</p>
%% <pre>
%% 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]}])
%% </pre>
%% </dd>
%% </dl>
%% </dd>
%% <dt>`gpb_codegen:format_fn(FnName, Fun [, RtTransforms]) -> iolist()'</dt>
%% <dd>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.</dd>
%% <dt>`?expr(Expr)' or
%% `gpb_codegen:expr(Expr)'</dt>
%% <dd>Will be replaced by the syntax tree for a `Expr'.</dd>
%% <dt>`?expr(Expr, RtTransforms)' or
%% `gpb_codegen:expr(Expr, RtTransforms)'</dt>
%% <dd>Like gpb_codegen:expr/1, but apply `RtTransforms' at run-time.</dd>
%% <dt>`?exprs(Expr, ..., RtTransforms)' or
%% `gpb_codegen:expr(Expr, ..., RtTransforms)'</dt>
%% <dd>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.</dd>
%% <dt>`?case_clause(Pattern [when Guard] -> Body)' or
%% `gpb_codegen:case_clause(CaseExpression)'</dt>
%% <dd><p>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.</p>
%% <p>Examples: `?case_clause(1 -> 2)' or `?case_clause(_ -> other)' or
%% `gpb_codegen:case_clause(case dummy of 1 -> 2 end)'.</p>
%% <p>In the macro form, some limitations apply:</p>
%% <ul>
%% <li>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.</li>
%% <li>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.</li>
%% </ul>
%% </dd>
%% <dt>`?case_clause(Pattern [when Guard] -> Body, RtTransforms)' or
%% `gpb_codegen:case_clause(CaseExpression, RtTransforms)'</dt>
%% <dd>Like `?case_clause/1' or `gpb_codegen:case_clause/1'
%% but apply the RtTransforms to the syntax tree.
%% </dd>
%% <dt>`?if_clause(Guard -> Body[, RtTransforms])' or
%% `gpb_codegen:if_clause(IfExpression[, RtTransforms])'</dt>
%% <dd>Like `?case_clause/1,2' but for if-clauses.</dd>
%% <dt>`?fn_clause(fun(...) -> ... end, [, RtTransforms])' or
%% `gpb_codegen:fn_clause(FunExpression[, RtTransforms])'</dt>
%% <dd>Like `?case_clause/1,2' but for function clauses.</dd>
%% <dt>`?receive_clause(Pattern -> Body, [, RtTransforms])' or
%% `gpb_codegen:receive_clause(receive ... -> ... end[, RtTransforms])'
%% </dt>
%% <dd>Like `?case_clause/1,2' but for receive clauses.</dd>
%% </dl>
%%
%% 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
%% @private
-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]).
-export([with_increased_backtrace_depth/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) ->
%% Allow very wide output. erl_prettypr tends
%% to otherwise format the generated code very widely,
%% breaking lines at the end of the lines,
%% giving a kind of boomerang-y shape,
%% somewhat
%% like
%% this text
Opts = [{paper, 300}, {ribbon, 250}],
with_increased_backtrace_depth(
fun() -> [erl_prettypr:format(Form, Opts), "\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.