Packages
gpb
4.3.3
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_gen_translators.erl
%%% Copyright (C) 2017 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 Generation of translation functions.
%%% This can translate google.protobuf.Any messages into more
%%% useful values, eg by doing yet another interior decoding or
%%% encoding.
%%%
%%% @private
-module(gpb_gen_translators).
-export([format_translators/3]).
-export([format_aux_transl_helpers/0]).
-export([format_merge_translators/3]).
-export([mk_find_tr_fn/3]).
-export([mk_find_tr_fn_elem/4]).
-export([mk_find_tr_fn_elem_or_default/3]).
-export([mk_find_tr_fn_elem_or_default/4]).
-export([mk_elempath_elem/3]).
-export([find_translation/3, find_translation/4]).
-export([has_translation/3]).
-export([has_type_spec_translation/2]).
-export([default_fn_by_op/2]).
-export([default_merge_translator/0]).
-export([default_verify_translator/0]).
-export([exists_tr_for_msg/3]).
-export([args_by_op2/1]).
-export([maybe_userdata_param/2]).
-include("../include/gpb.hrl").
-include("gpb_codegen.hrl").
-include("gpb_compile.hrl").
-import(gpb_lib, [replace_term/2, replace_tree/2, splice_trees/2]).
format_translators(_Defs, #anres{translations=Ts}=AnRes, Opts) ->
[[[format_field_op_translator(ElemPath, Op, CallTemplates, Opts)
|| {Op, CallTemplates} <- OpTransls,
Op /= type_spec]
|| {ElemPath, OpTransls} <- dict:to_list(Ts)],
format_default_translators(AnRes, Opts)].
format_merge_translators(_Defs, #anres{translations=Ts}=AnRes, Opts) ->
[[[format_field_op_translator(ElemPath, Op, CallTemplates, Opts)
|| {Op, CallTemplates} <- OpTransls,
Op == merge]
|| {ElemPath, OpTransls} <- dict:to_list(Ts)],
format_default_merge_translators(AnRes, Opts)].
format_field_op_translator(ElemPath, Op, CallTemplates, Opts) ->
FnName = mk_tr_fn_name(ElemPath, Op),
{InPatterns, Body} =
stack_transl_calls(
Op, lists:unzip(
[format_field_op_translator(Op, CallTemplate)
|| CallTemplate <- CallTemplates])),
[inline_attr(FnName,length(InPatterns)),
if Op == verify ->
%% Dialyzer might complain that "The created fun has no
%% local return", for a $errorf, which is true, but also
%% not surprising, so shut this warning down.
gpb_lib:nowarn_dialyzer_attr(FnName,length(InPatterns),Opts);
true ->
""
end,
gpb_codegen:format_fn(
FnName,
fun('$$InPatterns') ->
'$$Body'
end,
[splice_trees('$$InPatterns', InPatterns),
splice_trees('$$Body', Body)])].
%% When there are several translation calls, stack them like this:
%% Params:
%% Ins = list of function param patterns
%% for example: [(X, _, _), (X, Y, _), (X, _, _)]
%% Bodyies: list of calls,
%% for example [call1(X), call2(X, Y), call3(X, hello_world)]
%%
%% Assumption: Function param patterns are either single variable or underscore
%% Function param pattenrs that are variables all have the same
%% variable at each position, ie never like [(X, _), (Y, _)]
%%
%% Results:
%% Function param pattern: (X, Y, _) in the example, ie a variable
%% (the variable) at each position unless all Ins have underscore
%% at that position.
%% Body: In the example, the stacked body will be these three
%% expressions:
%% Res1 = call1(X),
%% Res2 = call2(Res1, Y),
%% call3(Res2, hello_world)
%% The decision to replace X and not say Y, with the variable of the
%% intermediate result is given by the out_to_in_by_op function.
stack_transl_calls(Op, {[Ins1 | RestInPatterns], [Body1 | RestBodies]}) ->
MergedBody =
gpb_lib:do_exprs(
fun({I, {InPatterns, Body}}, Var) ->
Pos = out_to_in_by_op(Op),
Body2 = replace_inpattern(lists:nth(Pos, Ins1), Body, Var),
%% New vars in body
VarsToKeep = extract_vars(InPatterns ++ [Var]),
unquify_free_vars(Body2, VarsToKeep, I)
end,
Body1,
gpb_lib:index_seq(lists:zip(RestInPatterns, RestBodies))),
MergedInPatterns =
lists:foldl(
fun(InPatterns, Acc) ->
[case {is_underscore(InPattern), is_underscore(AccPattern)} of
{true, true} -> AccPattern;
{false, true} -> InPattern;
{true, false} -> AccPattern;
{false, false} -> AccPattern % assume same var
end
|| {InPattern, AccPattern} <- lists:zip(InPatterns, Acc)]
end,
Ins1,
RestInPatterns),
{MergedInPatterns, MergedBody}.
replace_inpattern(InPattern, Body, Var) ->
erl_syntax_lib:map(
fun(Node) ->
if Node == InPattern -> Var;
true -> Node
end
end,
Body).
extract_vars(Patterns) ->
[erl_syntax:variable_name(Pat) || Pat <- Patterns,
erl_syntax:type(Pat) == variable].
unquify_free_vars(Body, VarsToKeep, I) ->
erl_syntax_lib:map(
fun(Node) ->
case erl_syntax:type(Node) of
variable ->
VarName = erl_syntax:variable_name(Node),
case lists:member(VarName, VarsToKeep) of
true ->
Node;
false ->
VarName1 = lists:concat([VarName, I]),
Node1 = erl_syntax:variable(VarName1),
erl_syntax:copy_pos(Node, Node1)
end;
_ ->
Node
end
end,
Body).
is_underscore(Pattern) ->
erl_syntax:type(Pattern) =:= underscore.
format_field_op_translator(Op, CallTemplate) ->
ArgTemplate = last_tuple_element(CallTemplate),
{InArgs, OutArgs0, Relations0} =
if Op /= verify ->
Ins = Outs = tr_in_args_by_op(Op),
InOutNames = [Name || {Name,_Value} <- Outs],
{Ins, Outs, mk_pass_straight_through_rel(InOutNames)};
Op == verify ->
[{_,V},{_,Path},{_,UserData}] = Ins = tr_in_args_by_op(Op),
ErrorF = ?expr(fun(Reason) ->
mk_type_error(Reason, 'Actual', 'Path')
end,
[replace_tree('Actual', V),
replace_tree('Path', Path)]),
Outs = [{'$1',V},{'$errorf',ErrorF},{'$user_data',UserData}],
Rels = [{'$1',['$1']},
%% $errorf uses $1,$2 if present, else $1,$2 are used
{'$errorf',['$1','$2'], ['$1','$2']},
{'$user_data',['$user_data']}],
{Ins, Outs, Rels}
end,
OutArgs = OutArgs0 ++ [{'$op', erl_syntax:abstract(Op)}],
Relations = Relations0 ++ [{'$op', []}],
{InPatterns, OutParams, _UsedInNames, UsedOutNames} =
process_tr_params(InArgs, Relations, OutArgs, ArgTemplate),
Call = case CallTemplate of
{Fn, ArgTemplate} ->
?expr('$$Fn'('$$OutParams'),
[replace_term('$$Fn', Fn),
splice_trees('$$OutParams', OutParams)]);
{Mod, Fn, ArgTemplate} ->
?expr('$$Mod':'$$Fn'('$$OutParams'),
[replace_term('$$Mod', Mod),
replace_term('$$Fn', Fn),
splice_trees('$$OutParams', OutParams)])
end,
UsesErrorF = lists:member('$errorf', UsedOutNames),
Body = if Op == verify, not UsesErrorF ->
[Actual,EPath|_] = InPatterns,
?expr(try '$$Call', ok
catch _:Reason ->
mk_type_error(Reason,'Actual','Path')
end,
[replace_tree('$$Call', Call),
replace_tree('Actual', Actual),
replace_tree('Path', EPath)]);
true ->
Call
end,
{InPatterns, Body}.
last_tuple_element(Tuple) ->
element(tuple_size(Tuple), Tuple).
%% InArgs = [{Name, SyntaxTree}] % eg: [{'$1',?expr(ToPackForEncode)}, ...]
%% Name = atom()
%% InOutArgRelations = [{OutParamName, [InArg1, InArg2, ...]}]
%% Example if InOutArgRelations (for verify translators):
%% [{'$1',['$1']},
%% {'$errorf', ['$1','$2']},
%% {'$user_data', ['$3']}]
%% Outs = [{Name, SyntaxTree}] % eg: [{'$1',?expr(ToPackForEncode)}, ...]
%% Name = atom()
%% ArgsTemplate = [term()] % eg: ['$1', '$2']
%% ff % or ['$user_data', ['$1', '$2', a, 4711]]
%% -> {InParams :: [syntax_tree()],
%% OutArgs :: [syntax_tree()],
%% UsedInArgs :: [atom()],
%% UsedOutParams :: [atom()]}
process_tr_params(InArgs, InOutArgRelations, Outs, ArgsTemplate) ->
{OutArgs, UsedOutNames} =
lists:mapfoldl(
fun(ArgTempl, Used) ->
{Out, MoreUsed} = abstractify_tr_param(ArgTempl, Outs),
{Out, lists:usort(MoreUsed ++ Used)}
end,
[],
ArgsTemplate),
UnusedOutNames = [N || {N,_} <- Outs] -- UsedOutNames,
UsedInArgs = compute_used_in_args(UsedOutNames, UnusedOutNames,
InOutArgRelations),
InParams = underscore_unused_params(InArgs, UsedInArgs),
{InParams, OutArgs, UsedInArgs, UsedOutNames}.
abstractify_tr_param([H|T], Outs) ->
{AbsH, UsedH} = abstractify_tr_param(H, Outs),
{AbsT, UsedT} = abstractify_tr_param(T, Outs),
{erl_syntax:cons(AbsH, AbsT), lists:usort(UsedH ++ UsedT)};
abstractify_tr_param([], _Outs) ->
{erl_syntax:nil(), []};
abstractify_tr_param(Tuple, Outs) when is_tuple(Tuple) ->
{AElems, AUsed} = lists:unzip([abstractify_tr_param(Elem, Outs)
|| Elem <- tuple_to_list(Tuple)]),
{erl_syntax:tuple(AElems), lists:usort(lists:append(AUsed))};
abstractify_tr_param(I, _Outs) when is_integer(I) ->
{erl_syntax:integer(I), []};
abstractify_tr_param(F, _Outs) when is_float(F) ->
{erl_syntax:float(F), []};
abstractify_tr_param(A, Outs) when is_atom(A) ->
case lists:keyfind(A, 1, Outs) of
{A,Abstr} -> {Abstr, [A]};
false -> {erl_syntax:atom(A), []}
end;
abstractify_tr_param(B, _Outs) when is_binary(B) ->
{erl_syntax:abstract(B), []};
abstractify_tr_param(B, _Outs) when is_bitstring(B) ->
%% Current version of erl_syntax (Erlang-18.3) can't do bitstrings,
%% but erl_parse can. Maybe future erl_syntax versions will...
try erl_syntax:abstract(B) of
STree -> {STree, []}
catch error:{badarg,_} ->
erl_parse:abstract(B)
end;
abstractify_tr_param(X, Outs) ->
abstractify_tr_param_check_for_map(X, Outs).
-ifdef(NO_HAVE_MAPS).
abstractify_tr_param_check_for_map(X, _Outs) ->
error({translator,cant_make_abstraxt_code_for,X}).
-else.
abstractify_tr_param_check_for_map(M, Outs) when is_map(M) ->
{MItems, MUsed} =
lists:unzip([begin
{AK,UK} = abstractify_tr_param(K, Outs),
{AV,UV} = abstractify_tr_param(V, Outs),
{erl_syntax:map_field_assoc(AK, AV), UK ++ UV}
end
|| {K,V} <- maps:to_list(M)]),
{erl_syntax:map_expr(MItems), lists:usort(lists:append(MUsed))};
abstractify_tr_param_check_for_map(X, _Outs) ->
error({translator,cant_make_abstraxt_code_for,X}).
-endif. % NO_HAVE_MAPS.
mk_pass_straight_through_rel(Names) ->
[{Name,[Name]} || Name <- Names].
compute_used_in_args(Used, Unused, InOutArgRelations) ->
lists:usort(
lists:append(
[lists:append(
[case lists:keyfind(U, 1, InOutArgRelations) of
{U, Ins} -> Ins;
{U, Ins, _Elses} -> Ins;
false -> []
end
|| U <- Used]),
lists:append(
[case lists:keyfind(Uu, 1, InOutArgRelations) of
{Uu, _Ins} -> [];
{Uu, _Ins, Elses} -> Elses;
false -> []
end
|| Uu <- Unused])])).
underscore_unused_params(InArgs, UsedInArgs) ->
[case lists:member(InName, UsedInArgs) of
true -> InExpr;
false -> ?expr(_)
end
|| {InName, InExpr} <- InArgs].
dollar_i(Name) ->
list_to_atom(?ff("$~w", [Name])).
tr_in_args_by_op(Op) ->
[{dollar_i(I), A} || {I,A} <- gpb_lib:index_seq(args_by_op2(Op))]
++ [{'$user_data', ?expr(TrUserData)}].
args_by_op2(encode) -> [?expr(X)];
args_by_op2(decode) -> [?expr(X)];
args_by_op2(decode_init_default) -> [?expr(InitialValue)];
args_by_op2(decode_repeated_add_elem) -> [?expr(Elem), ?expr(L)];
args_by_op2(decode_repeated_finalize) -> [?expr(L)];
args_by_op2(merge) -> [?expr(X1), ?expr(X2)];
args_by_op2(verify) -> [?expr(V), ?expr(Path)].
format_aux_transl_helpers() ->
[gpb_lib:nowarn_unused_function(id,2),
inline_attr(id,2),
"id(X, _TrUserData) -> X.\n",
"\n",
gpb_lib:nowarn_unused_function(v_ok,3),
inline_attr(v_ok,3),
"v_ok(_Value, _Path, _TrUserData) -> ok.\n",
"\n",
gpb_lib:nowarn_unused_function(m_overwrite,3),
inline_attr(m_overwrite,3),
"m_overwrite(_Prev, New, _TrUserData) -> New.\n",
"\n",
gpb_lib:nowarn_unused_function(cons,3),
inline_attr(cons,3),
"cons(Elem, Acc, _TrUserData) -> [Elem | Acc].\n",
"\n",
gpb_lib:nowarn_unused_function('lists_reverse',2),
inline_attr('lists_reverse',2),
"'lists_reverse'(L, _TrUserData) -> lists:reverse(L)."
"\n",
gpb_lib:nowarn_unused_function('erlang_++',3),
inline_attr('erlang_++',3),
"'erlang_++'(A, B, _TrUserData) -> A ++ B."
"\n"].
format_default_translators(AnRes, Opts) ->
[format_default_map_translators(AnRes, Opts),
format_default_msg_translators(AnRes, Opts)].
format_default_map_translators(#anres{map_types=MapTypes,
map_value_types=MVT}=AnRes, Opts) ->
HaveMaps = sets:size(MapTypes) > 0,
{HaveMapSubmsgs, HaveMapNonSubmsgs} = MVT,
[%% Auxiliary helpers in case of fields of type map<_,_>
[case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of
'2tuples' ->
[inline_attr(mt_maptuple_to_pseudomsg_r,2),
gpb_codegen:format_fn(
mt_maptuple_to_pseudomsg_r,
fun({K,V},RName) -> {RName,K,V} end),
"\n",
inline_attr(mt_empty_map_r,0),
gpb_codegen:format_fn(
mt_empty_map_r,
fun() -> dict:new() end),
[[inline_attr(mt_add_item_r,2),
gpb_codegen:format_fn(
mt_add_item_r,
fun({_RName,K,V}, D) -> dict:store(K,V,D) end),
"\n"]
|| HaveMapNonSubmsgs],
[[inline_attr(mt_add_item_r_verify_value,2),
gpb_codegen:format_fn(
mt_add_item_r_verify_value,
fun({_,_,undefined}, _) -> error({gpb_error, missing_value});
({_RName,K,V}, D) -> dict:store(K,V,D)
end),
"\n"]
|| HaveMapSubmsgs],
inline_attr(mt_finalize_items_r,1),
gpb_codegen:format_fn(
mt_finalize_items_r,
fun(D) -> dict:to_list(D) end),
"\n"];
maps ->
{M,K,V} = {?expr(M), ?expr(K), ?expr(V)},
[inline_attr(mt_maptuple_to_pseudomsg_m,1),
gpb_codegen:format_fn(
mt_maptuple_to_pseudomsg_m,
fun({K,V}) -> '#{key => K, value => V}' end,
[replace_tree('#{key => K, value => V}',
gpb_lib:map_create([{key,K}, {value,V}],
Opts))]),
"\n",
inline_attr(mt_map_to_list_m,1),
gpb_codegen:format_fn(
mt_map_to_list_m,
fun(M) -> maps:to_list(M) end),
"\n",
inline_attr(mt_empty_map_m,0),
gpb_codegen:format_fn(
mt_empty_map_m,
fun() -> '#{}' end,
[replace_tree('#{}', gpb_lib:map_create([], []))]),
"\n",
[[inline_attr(mt_add_item_m,2),
case gpb_lib:target_has_variable_key_map_update(Opts) of
true ->
gpb_codegen:format_fn(
mt_add_item_m,
fun('#{key := K,value := V}', M) -> 'M#{K => V}' end,
[replace_tree(
'#{key := K,value := V}',
gpb_lib:map_match([{key,K}, {value,V}], Opts)),
replace_tree(
'M#{K => V}',
gpb_lib:map_set(M, [{K,V}], []))]);
false ->
gpb_codegen:format_fn(
mt_add_item_m,
fun('#{key := K,value := V}', M) ->
maps:put('K', 'V', 'M')
end,
[replace_tree(
'#{key := K,value := V}',
gpb_lib:map_match([{key,K}, {value,V}], Opts)),
replace_tree('K', K),
replace_tree('V', V),
replace_tree('M', M)])
end]
|| HaveMapNonSubmsgs],
[[inline_attr(mt_add_item_m_verify_value,2),
case gpb_lib:target_has_variable_key_map_update(Opts) of
true ->
gpb_codegen:format_fn(
mt_add_item_m_verify_value,
fun('#{key := K,value := V}', M) ->
if V =:= '$undef' ->
error({gpb_error, missing_value});
true ->
'M#{K => V}'
end
end,
[replace_tree(
'#{key := K,value := V}',
gpb_lib:map_match([{key,K}, {value,V}], Opts)),
replace_tree(
'M#{K => V}',
gpb_lib:map_set(M, [{K,V}], []))]);
false ->
gpb_codegen:format_fn(
mt_add_item_m_verify_value,
fun('#{key := K,value := V}', M) ->
if V =:= '$undef' ->
error({gpb_error, missing_value});
true ->
maps:put('K', 'V', 'M')
end
end,
[replace_tree(
'#{key := K,value := V}',
gpb_lib:map_match([{key,K}, {value,V}], Opts)),
replace_tree('K', K),
replace_tree('V', V),
replace_tree('M', M)])
end]
|| HaveMapSubmsgs],
"\n"]
end,
format_default_merge_translators(AnRes, Opts)]
|| HaveMaps].
format_default_merge_translators(#anres{map_types=MapTypes}, Opts) ->
HaveMaps = sets:size(MapTypes) > 0,
[%% Auxiliary helpers in case of fields of type map<_,_>
case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of
'2tuples' ->
[inline_attr(mt_merge_maptuples_r,2),
gpb_codegen:format_fn(
mt_merge_maptuples_r,
fun(L1, L2) ->
dict:to_list(dict:merge(fun(_Key, _V1, V2) -> V2 end,
dict:from_list(L1),
dict:from_list(L2)))
end),
"\n"];
maps ->
[inline_attr(mt_merge_maps_m,2),
gpb_codegen:format_fn(
mt_merge_maps_m,
fun(M1, M2) -> maps:merge(M1,M2) end),
"\n"]
end
|| HaveMaps].
format_default_msg_translators(#anres{translations=Translations}, _Opts) ->
Defaults = [{merge, default_merge_translator()},
{verify, default_verify_translator()}],
Needs = compute_needed_default_translations(Translations, Defaults),
[[[inline_attr(msg_m_overwrite,2),
gpb_codegen:format_fn(
msg_m_overwrite,
fun(Msg2,_) -> Msg2 end),
"\n"] || sets:is_element(merge, Needs)],
[[gpb_codegen:format_fn(
msg_v_no_check,
fun(_,_) -> ok end),
"\n"] || sets:is_element(verify, Needs)]].
compute_needed_default_translations(Translations, Defaults) ->
dict:fold(
fun(_ElemPath, Ops, Acc) ->
lists:foldl(
fun({type_spec, _}, Acc2) ->
Acc2;
({Op, Calls}, Acc2) ->
lists:foldl(
fun(Call, Acc3) ->
case lists:member({Op, Call}, Defaults) of
true -> sets:add_element(Op, Acc3);
false -> Acc3
end
end,
Acc2,
Calls)
end,
Acc,
Ops)
end,
sets:new(),
Translations).
inline_attr(FnName,Arity) ->
?f("-compile({inline,~p/~w}).~n", [FnName,Arity]).
%% -- auxiliary helpers, also used from encoders/decoders/... ------------
mk_find_tr_fn(MsgName, #?gpb_field{name=FName}, AnRes) ->
ElemPath = [MsgName,FName],
fun(Op) -> find_translation(ElemPath, Op, AnRes) end;
mk_find_tr_fn(MsgName, #gpb_oneof{name=CFName}, AnRes) ->
fun({update_elem_path,FName}) ->
fun(Op) ->
ElemPath = [MsgName,CFName,FName],
find_translation(ElemPath, Op, AnRes)
end
end.
mk_find_tr_fn_elem(MsgName, Field, IsOneof, AnRes) ->
ElemPath = mk_elempath_elem(MsgName, Field, IsOneof),
fun(Op) -> find_translation(ElemPath, Op, AnRes) end.
mk_find_tr_fn_elem_or_default(MsgName, {Field, IsOneof}=_XField, AnRes) ->
mk_find_tr_fn_elem_or_default(MsgName, Field, IsOneof, AnRes).
mk_find_tr_fn_elem_or_default(MsgName, Field, IsOneof, AnRes) ->
ElemPath = mk_elempath_elem(MsgName, Field, IsOneof),
fun(Op, Default) -> find_translation(ElemPath, Op, AnRes, Default) end.
mk_elempath_elem(MsgName, #?gpb_field{name=FName,occurrence=Occ}, false) ->
case Occ of
repeated -> [MsgName,FName,[]];
_ -> [MsgName,FName]
end;
mk_elempath_elem(MsgName, #?gpb_field{name=FName}, {true, CFName}) ->
[MsgName,CFName,FName].
find_translation(ElemPath, Op, AnRes) ->
find_translation(ElemPath, Op, AnRes, undefined).
find_translation(ElemPath, Op, AnRes, Default) ->
case has_translation(ElemPath, Op, AnRes) of
{true, Transl} ->
Transl;
false ->
default_fn_by_op(Op, Default)
end.
has_translation(ElemPath, Op, #anres{translations=Ts}) ->
case dict:find(ElemPath, Ts) of
{ok, OpTransls} ->
case lists:keyfind(Op, 1, OpTransls) of
{Op, _Calls} ->
{true, mk_tr_fn_name(ElemPath, Op)};
false ->
false
end;
error ->
false
end.
has_type_spec_translation(ElemPath, #anres{translations=Ts}) ->
case dict:find(ElemPath, Ts) of
{ok, OpTransls} ->
case lists:keyfind(type_spec, 1, OpTransls) of
{type_spec, TypeSpec} when is_list(TypeSpec) ->
{true, TypeSpec};
{type_spec, '$default'} ->
false;
false ->
false
end;
error ->
false
end.
mk_tr_fn_name([MsgName], Op) ->
list_to_atom(?ff("tr_~s_~s", [Op, MsgName]));
mk_tr_fn_name([MsgName,FieldName,[]], Op) ->
list_to_atom(?ff("tr_~s_~s.~s[x]", [Op, MsgName,FieldName]));
mk_tr_fn_name([MsgName,FName,OneofName], Op) ->
list_to_atom(?ff("tr_~s_~s.~s.~s", [Op, MsgName,FName,OneofName]));
mk_tr_fn_name([MsgName,FieldName], Op) ->
list_to_atom(?ff("tr_~s_~s.~s", [Op, MsgName,FieldName])).
default_fn_by_op(decode_repeated_add_elem, undefined) ->
cons;
default_fn_by_op(decode_repeated_finalize, undefined) ->
lists_reverse;
default_fn_by_op(merge, undefined) ->
m_overwrite;
default_fn_by_op(verify, undefined) ->
v_ok;
default_fn_by_op(_, undefined) ->
id;
default_fn_by_op(_, Fn) ->
Fn.
out_to_in_by_op(merge) -> 2;
out_to_in_by_op(_) -> 1.
default_merge_translator() -> {msg_m_overwrite,['$2','$user_data']}.
default_verify_translator() -> {msg_v_no_check,['$1', '$user_data']}.
exists_tr_for_msg(MsgName, Op, #anres{translations=Translations}) ->
dict:fold(fun(_Key, _OpCalls, true) ->
true;
([Name,_Field|_], OpCalls, false) when Name == MsgName ->
lists:keymember(Op, 1, OpCalls);
(_Key, _OpCalls, false) ->
false
end,
false,
Translations).
maybe_userdata_param(Field, Expr) ->
case is_primitive_type(Field) of
true -> [];
false -> [Expr]
end.
is_primitive_type(#?gpb_field{type={msg,_}}) -> false;
is_primitive_type(#?gpb_field{type={group,_}}) -> false;
is_primitive_type(#?gpb_field{type={map,_,_}}) -> false;
is_primitive_type(_) -> true.