Packages
gpb
4.2.2
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/1]).
-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([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, CallTemplate, Opts)
|| {Op, CallTemplate} <- OpTransls]
|| {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, CallTemplate, Opts)
|| {Op, CallTemplate} <- OpTransls,
Op == merge]
|| {ElemPath, OpTransls} <- dict:to_list(Ts)],
format_default_merge_translators(AnRes, Opts)].
format_field_op_translator(ElemPath, Op, CallTemplate, Opts) ->
ArgTemplate = last_tuple_element(CallTemplate),
FnName = mk_tr_fn_name(ElemPath, Op),
{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,
[inline_attr(FnName,length(InArgs)),
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(InArgs),Opts);
true ->
""
end,
gpb_codegen:format_fn(
FnName,
fun('$$InPatterns') ->
'$$Body'
end,
[splice_trees('$$InPatterns', InPatterns),
replace_tree('$$Body', 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(#anres{default_transls=UsedDefaultTransls}) ->
IsNeeded = fun(F,A) -> sets:is_element({F,A}, UsedDefaultTransls) end,
[[[inline_attr(id,2),
"id(X, _TrUserData) -> X.\n",
"\n"] || IsNeeded(id,2)],
[[inline_attr(cons,3),
"cons(Elem, Acc, _TrUserData) -> [Elem | Acc].\n",
"\n"] || IsNeeded(cons,3)],
[[inline_attr('lists_reverse',2),
"'lists_reverse'(L, _TrUserData) -> lists:reverse(L)."
"\n"] || IsNeeded(lists_reverse,2)],
[[inline_attr('erlang_++',3),
"'erlang_++'(A, B, _TrUserData) -> A ++ B."
"\n"] || IsNeeded('erlang_++',3)]].
format_default_translators(AnRes, Opts) ->
[format_default_map_translators(AnRes, Opts),
format_default_any_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}]))]),
"\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}])),
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}])),
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}])),
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}])),
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_any_translators(#anres{translations=Translations}, _Opts) ->
Defaults = [{merge, default_merge_translator()},
{verify, default_verify_translator()}],
Needs = compute_needed_default_translations(Translations, Defaults),
[[[inline_attr(any_m_overwrite,2),
gpb_codegen:format_fn(
any_m_overwrite,
fun(Any2,_) -> Any2 end),
"\n"] || sets:is_element(merge, Needs)],
[[gpb_codegen:format_fn(
any_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({Op, Call}, Acc2) ->
case lists:member({Op, Call}, Defaults) of
true -> sets:add_element(Op, Acc2);
false -> Acc2
end
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{translations=Ts}, Default) ->
case dict:find(ElemPath, Ts) of
{ok, OpTransls} ->
case lists:keyfind(Op, 1, OpTransls) of
{Op, _Fn} ->
mk_tr_fn_name(ElemPath, Op);
false ->
default_fn_by_op(Op, Default)
end;
error ->
default_fn_by_op(Op, Default)
end.
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(_, undefined) ->
id;
default_fn_by_op(_, Fn) ->
Fn.
default_merge_translator() -> {any_m_overwrite,['$2','$user_data']}.
default_verify_translator() -> {any_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.