%%% Copyright (C) 2017 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 Generation of encoding functions. %%% @private -module(gpb_gen_encoders). -export([format_encoders_top_function/2]). -export([format_msg_encoders/4]). -export([format_map_encoders/4]). -export([format_aux_encoders/3]). -include("../include/gpb.hrl"). -include("gpb_codegen.hrl"). -include("gpb_compile.hrl"). -import(gpb_lib, [replace_term/2, replace_tree/2, splice_trees/2, repeat_clauses/2]). %% -- encoders ----------------------------------------------------- format_encoders_top_function(Defs, Opts) -> case gpb_lib:contains_messages(Defs) of true -> format_encoders_top_function_msgs(Defs, Opts); false -> format_encoders_top_function_no_msgs(Opts) end. format_encoders_top_function_no_msgs(Opts) -> Mapping = gpb_lib:get_records_or_maps_by_opts(Opts), MsgNameVars = case Mapping of records -> []; maps -> [?expr(_MsgName)] end, SpecExtraArgs = case Mapping of records -> ""; maps -> ",_" end, [?f("-spec encode_msg(_~s) -> no_return().~n", [SpecExtraArgs]), gpb_codegen:format_fn( encode_msg, fun(Msg, '') -> encode_msg(Msg, '', []) end, [splice_trees('', MsgNameVars)]), "\n", ?f("-spec encode_msg(_,_~s) -> no_return().~n", [SpecExtraArgs]), gpb_codegen:format_fn( encode_msg, fun(_Msg, '', _Opts) -> erlang:error({gpb_error, no_messages}) end, [splice_trees('', MsgNameVars)]), "\n", [[?f("%% epb compatibility\n"), ?f("-spec encode(_) -> no_return().\n"), gpb_codegen:format_fn( encode, fun(_Msg) -> erlang:error({gpb_error, no_messages}) end)] || gpb_lib:get_epb_functions_by_opts(Opts)]]. format_encoders_top_function_msgs(Defs, Opts) -> Verify = proplists:get_value(verify, Opts, optionally), Mapping = gpb_lib:get_records_or_maps_by_opts(Opts), MsgNames = gpb_lib:msg_names(Defs), {MsgNameVars, MsgType, SpecExtraArgs} = case Mapping of records -> {[], gpb_lib:or_join([?f("#~p{}", [M]) || M <- MsgNames]), ""}; maps -> MsgNameType = gpb_lib:or_join( [?f("~p", [M]) || M <- MsgNames]), MsgMapType = case gpb_lib:get_type_specs_by_opts(Opts) of false -> "map()"; true -> gpb_lib:or_join([?f("~p()", [M]) || M <- MsgNames]) end, {[?expr(MsgName)], MsgMapType, "," ++ MsgNameType} end, DoNif = proplists:get_bool(nif, Opts), [?f("-spec encode_msg(~s~s) -> binary().~n", [MsgType, SpecExtraArgs]), gpb_codegen:format_fn( encode_msg, fun(Msg, '') -> encode_msg(Msg, '', []) end, [splice_trees('', MsgNameVars)]), "\n", ?f("-spec encode_msg(~s~s, list()) -> binary().~n", [MsgType, SpecExtraArgs]), gpb_codegen:format_fn( encode_msg, fun(Msg, '', '') -> '', 'TrUserData = proplists:get_value(user_data, Opts)', case '' of '' -> 'encode'(Msg, 'TrUserData') end end, [replace_tree('', case {DoNif, Verify} of {true,optionally} -> ?expr(Opts); {true,always} -> ?expr(Opts); {true,never} -> ?expr(_Opts); {false,_} -> ?expr(Opts) end), splice_trees('', case Verify of optionally -> [?expr(case proplists:get_bool(verify, Opts) of true -> verify_msg(Msg, '', Opts); false -> ok end)]; always -> [?expr(verify_msg(Msg, '', Opts))]; never -> [] end), splice_trees( 'TrUserData = proplists:get_value(user_data, Opts)', if DoNif -> []; true -> [?expr(TrUserData = proplists:get_value(user_data, Opts))] end), repeat_clauses('', [[replace_tree( '', case Mapping of records -> gpb_lib:record_match(MsgName, []); maps -> erl_syntax:atom(MsgName) end), replace_term('encode', gpb_lib:mk_fn(e_msg_, MsgName))] || {{msg,MsgName}, _Fields} <- Defs]), replace_tree('', case Mapping of records -> ?expr(Msg); maps -> ?expr(MsgName) end), splice_trees('', MsgNameVars), splice_trees('TrUserData', if DoNif -> []; true -> [?expr(TrUserData)] end)]), "\n", [[?f("%% epb compatibility\n"), ?f("-spec encode(_) -> binary().~n"), gpb_codegen:format_fn( encode, fun(Msg) -> encode_msg(Msg) end), [[?f("-spec ~p(_) -> binary().~n", [gpb_lib:mk_fn(encode_, MsgName)]), gpb_codegen:format_fn( gpb_lib:mk_fn(encode_, MsgName), fun(Msg) -> encode_msg(Msg) end)] || {{msg,MsgName}, _Fields} <- Defs]] || gpb_lib:get_epb_functions_by_opts(Opts)]]. format_aux_encoders(Defs, AnRes, _Opts) -> [format_enum_encoders(Defs, AnRes), format_type_encoders(AnRes), format_is_empty_string(AnRes) ]. format_enum_encoders(Defs, #anres{used_types=UsedTypes}) -> [gpb_codegen:format_fn( gpb_lib:mk_fn(e_enum_, EnumName), fun('', Bin) -> <'>>; (V, Bin) -> % integer (for yet unknown enums) e_varint(V, Bin) end, [repeat_clauses('', [begin ViBytes = enum_to_binary_fields(EnumValue), [replace_term('', EnumSym), splice_trees('', ViBytes)] end || {EnumSym, EnumValue} <- EnumDef])]) || {{enum, EnumName}, EnumDef} <- Defs, gpb_lib:smember({enum,EnumName}, UsedTypes)]. format_map_encoders(Defs, AnRes, Opts0, IncludeStarter) -> Opts1 = case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts0) of '2tuples' -> [{msgs_as_maps, false} | Opts0]; maps -> [{msgs_as_maps, true} | Opts0] end, format_msg_encoders(Defs, AnRes, Opts1, IncludeStarter). format_msg_encoders(Defs, AnRes, Opts, IncludeStarter) -> [[format_msg_encoder(MsgName, MsgDef, Defs, AnRes, Opts, if Type =:= group -> false; true -> IncludeStarter end) || {Type, MsgName, MsgDef} <- gpb_lib:msgs_or_groups(Defs)], format_special_field_encoders(Defs, AnRes)]. format_msg_encoder(MsgName, [], _Defs, _AnRes, _Opts, IncludeStarter) -> case IncludeStarter of true -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_msg_, MsgName), fun(_Msg, _TrUserData) -> <<>> end); false -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_msg_, MsgName), fun(_Msg, Bin, _TrUserData) -> Bin end) end; format_msg_encoder(MsgName, MsgDef, Defs, AnRes, Opts, IncludeStarter) -> FNames = gpb_lib:get_field_names(MsgDef), FVars = [gpb_lib:var_f_n(I) || I <- lists:seq(1, length(FNames))], BVars = [gpb_lib:var_b_n(I) || I <- lists:seq(1, length(FNames)-1)] ++ [last], MsgVar = ?expr(M), TrUserDataVar = ?expr(TrUserData), {EncodeExprs, _} = lists:mapfoldl( fun({NewBVar, Field, FVar}, PrevBVar) when NewBVar /= last -> Tr = gpb_gen_translators:mk_find_tr_fn(MsgName, Field, AnRes), EncExpr = field_encode_expr(MsgName, MsgVar, Field, FVar, PrevBVar, TrUserDataVar, Defs, Tr, AnRes, Opts), E = ?expr('' = '', [replace_tree('', NewBVar), replace_tree('', EncExpr)]), {E, NewBVar}; ({last, Field, FVar}, PrevBVar) -> Tr = gpb_gen_translators:mk_find_tr_fn(MsgName, Field, AnRes), EncExpr = field_encode_expr(MsgName, MsgVar, Field, FVar, PrevBVar, TrUserDataVar, Defs, Tr, AnRes, Opts), {EncExpr, dummy} end, ?expr(Bin), lists:zip3(BVars, MsgDef, FVars)), FnName = gpb_lib:mk_fn(e_msg_, MsgName), FieldMatching = case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> gpb_lib:mapping_match(MsgName, lists:zip(FNames, FVars), Opts); #maps{unset_optional=present_undefined} -> gpb_lib:mapping_match(MsgName, lists:zip(FNames, FVars), Opts); #maps{unset_optional=omitted} -> FMap = gpb_lib:zip_for_non_opt_fields(MsgDef, FVars), if length(FMap) == length(FNames) -> gpb_lib:map_match(FMap); length(FMap) < length(FNames) -> ?expr('mapmatch' = 'M', [replace_tree('mapmatch', gpb_lib:map_match(FMap)), replace_tree('M', MsgVar)]) end end, [[[gpb_codegen:format_fn( FnName, fun(Msg, TrUserData) -> call_self(Msg, <<>>, TrUserData) end), "\n"] || IncludeStarter], gpb_codegen:format_fn( FnName, fun('', Bin, TrUserData) -> '' end, [replace_tree('', FieldMatching), splice_trees('', EncodeExprs)])]. field_encode_expr(MsgName, MsgVar, #?gpb_field{name=FName}=Field, FVar, PrevBVar, TrUserDataVar, Defs, Tr, _AnRes, Opts)-> FEncoder = mk_field_encode_fn_name(MsgName, Field), #?gpb_field{occurrence=Occurrence, type=Type, fnum=FNum, name=FName}=Field, TrFVar = gpb_lib:prefix_var("Tr", FVar), Transforms = [replace_term('fieldname', FName), replace_tree('', FVar), replace_tree('TrF', TrFVar), replace_term('Tr', Tr(encode)), replace_tree('TrUserData', TrUserDataVar), splice_trees('MaybeTrUserData', gpb_gen_translators:maybe_userdata_param( Field, TrUserDataVar)), replace_term('', FEncoder), replace_tree('', PrevBVar), splice_trees('', key_to_binary_fields(FNum, Type))], IsEnum = case Type of {enum,_} -> true; _ -> false end, case Occurrence of optional -> EncodeExpr = case gpb:is_msg_proto3(MsgName, Defs) of false -> ?expr(begin 'TrF' = 'Tr'('', 'TrUserData'), ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end, Transforms); true when Type == string -> ?expr(begin 'TrF' = 'Tr'('', 'TrUserData'), case is_empty_string('TrF') of true -> ''; false -> ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end end, Transforms); true when Type == bytes -> ?expr(begin 'TrF' = 'Tr'('', 'TrUserData'), case iolist_size('TrF') of 0 -> ''; _ -> ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end end, Transforms); true when IsEnum -> TypeDefault = gpb:proto3_type_default(Type, Defs), ?expr( begin 'TrF' = 'Tr'('', 'TrUserData'), if 'TrF' =:= ''; 'TrF' =:= 0 -> ''; true -> ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end end, [replace_term('', TypeDefault) | Transforms]); true -> TypeDefault = gpb:proto3_type_default(Type, Defs), ?expr( begin 'TrF' = 'Tr'('', 'TrUserData'), if 'TrF' =:= '' -> ''; true -> ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end end, [replace_term('', TypeDefault) | Transforms]) end, case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> ?expr( if '' == undefined -> ''; true -> '' end, [replace_tree('', EncodeExpr) | Transforms]); #maps{unset_optional=present_undefined} -> ?expr( if '' == undefined -> ''; true -> '' end, [replace_tree('', EncodeExpr) | Transforms]); #maps{unset_optional=omitted} -> ?expr( case 'M' of '#{fieldname := }' -> ''; _ -> '' end, [replace_tree('M', MsgVar), replace_tree('#{fieldname := }', gpb_lib:map_match([{FName,FVar}])), replace_tree('', EncodeExpr) | Transforms]) end; repeated -> case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> ?expr( begin 'TrF' = 'Tr'('', 'TrUserData'), if 'TrF' == [] -> ''; true -> ''('TrF', '', 'TrUserData') end end, Transforms); #maps{unset_optional=present_undefined} -> ?expr( begin 'TrF' = 'Tr'('', 'TrUserData'), if 'TrF' == [] -> ''; true -> ''('TrF', '', 'TrUserData') end end, Transforms); #maps{unset_optional=omitted} -> ?expr( case 'M' of '#{fieldname := }' -> 'TrF' = 'Tr'('', 'TrUserData'), if 'TrF' == [] -> ''; true -> ''('TrF', '', 'TrUserData') end; _ -> '' end, [replace_tree('M', MsgVar), replace_tree('#{fieldname := }', gpb_lib:map_match([{FName,FVar}])) | Transforms]) end; required -> ?expr( begin 'TrF' = 'Tr'('', 'TrUserData'), ''('TrF', <<''/binary, ''>>, 'MaybeTrUserData') end, Transforms) end; field_encode_expr(MsgName, MsgVar, #gpb_oneof{name=FName, fields=OFields}, FVar, PrevBVar, TrUserDataVar, Defs, Tr, AnRes, Opts) -> OFVar = gpb_lib:prefix_var("O", FVar), OneofClauseTransforms = [begin OFVal = ?expr({'', ''}, [replace_term('', Name), replace_tree('', OFVar)]), MatchPattern = case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> OFVal; #maps{unset_optional=present_undefined} -> OFVal; #maps{unset_optional=omitted, oneof=tuples} -> gpb_lib:map_match([{FName, OFVal}]); #maps{unset_optional=omitted, oneof=flat} -> gpb_lib:map_match([{Name, OFVar}]) end, %% undefined is already handled, we have a match, %% the field occurs, as if it had been required OField2 = OField#?gpb_field{occurrence=required}, Tr2 = Tr({update_elem_path,Name}), EncExpr = field_encode_expr(MsgName, MsgVar, OField2, OFVar, PrevBVar, TrUserDataVar, Defs, Tr2, AnRes, Opts), [replace_tree('', MatchPattern), replace_tree('', EncExpr)] end || #?gpb_field{name=Name}=OField <- OFields], case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> ?expr(case '' of undefined -> ''; '' -> '' end, [replace_tree('', FVar), replace_tree('', PrevBVar), repeat_clauses('', OneofClauseTransforms)]); #maps{unset_optional=present_undefined} -> ?expr(case '' of undefined -> ''; '' -> '' end, [replace_tree('', FVar), replace_tree('', PrevBVar), repeat_clauses('', OneofClauseTransforms)]); #maps{unset_optional=omitted} -> ?expr(case 'M' of '' -> ''; _ -> '' end, [replace_term('fieldname', FName), replace_tree('M', MsgVar), replace_tree('', PrevBVar), repeat_clauses('', OneofClauseTransforms)]) end. mk_field_encode_fn_name(MsgName, #?gpb_field{occurrence=repeated, name=FName})-> gpb_lib:mk_fn(e_field_, MsgName, FName); mk_field_encode_fn_name(MsgName, #?gpb_field{type={msg,_Msg}, name=FName}) -> gpb_lib:mk_fn(e_mfield_, MsgName, FName); mk_field_encode_fn_name(MsgName, #?gpb_field{type={group,_Nm}, name=FName}) -> gpb_lib:mk_fn(e_mfield_, MsgName, FName); mk_field_encode_fn_name(_MsgName, #?gpb_field{type={enum,EnumName}}) -> gpb_lib:mk_fn(e_enum_, EnumName); mk_field_encode_fn_name(_MsgName, #?gpb_field{type=sint32}) -> gpb_lib:mk_fn(e_type_, sint); mk_field_encode_fn_name(_MsgName, #?gpb_field{type=sint64}) -> gpb_lib:mk_fn(e_type_, sint); mk_field_encode_fn_name(_MsgName, #?gpb_field{type=uint32}) -> e_varint; mk_field_encode_fn_name(_MsgName, #?gpb_field{type=uint64}) -> e_varint; mk_field_encode_fn_name(MsgName, #?gpb_field{type=Type}=F) -> case Type of {map,KeyType,ValueType} -> MapAsMsgMame = gpb_lib:map_type_to_msg_name(KeyType, ValueType), F2 = F#?gpb_field{type = {msg,MapAsMsgMame}}, mk_field_encode_fn_name(MsgName, F2); _ -> gpb_lib:mk_fn(e_type_, Type) end. format_special_field_encoders(Defs, AnRes) -> lists:reverse( %% so generated auxiliary functions come in logical order gpb_lib:fold_msg_or_group_fields( fun(_Type, MsgName, #?gpb_field{occurrence=repeated}=FieldDef, Acc) -> [format_field_encoder(MsgName, FieldDef, AnRes) | Acc]; (_Type, MsgName, #?gpb_field{type={msg,_}}=FieldDef, Acc)-> [format_field_encoder(MsgName, FieldDef, AnRes) | Acc]; (_Type, MsgName, #?gpb_field{type={group,_}}=FieldDef, Acc)-> [format_field_encoder(MsgName, FieldDef, AnRes) | Acc]; (_Type, _MsgName, #?gpb_field{}, Acc) -> Acc end, [], Defs)). format_field_encoder(MsgName, FieldDef, AnRes) -> #?gpb_field{occurrence=Occurrence} = FieldDef, RFieldDef = FieldDef#?gpb_field{occurrence=required}, [possibly_format_mfield_encoder(MsgName, RFieldDef, AnRes), case {Occurrence, gpb_lib:is_packed(FieldDef)} of {repeated, false} -> format_repeated_field_encoder2(MsgName, FieldDef, AnRes); {repeated, true} -> format_packed_field_encoder2(MsgName, FieldDef, AnRes); {optional, false} -> []; {required, false} -> [] end]. possibly_format_mfield_encoder(MsgName, #?gpb_field{type={msg,SubMsg}}=FieldDef, AnRes) -> FnName = mk_field_encode_fn_name(MsgName, FieldDef), case is_msgsize_known_at_generationtime(SubMsg, AnRes) of no -> gpb_codegen:format_fn( FnName, fun(Msg, Bin, TrUserData) -> SubBin = ''(Msg, <<>>, TrUserData), Bin2 = e_varint(byte_size(SubBin), Bin), <> end, [replace_term('', gpb_lib:mk_fn(e_msg_, SubMsg))]); {yes, MsgSize} when MsgSize > 0 -> MsgSizeBytes = gpb_lib:varint_to_binary_fields(MsgSize), gpb_codegen:format_fn( FnName, fun(Msg, Bin, TrUserData) -> Bin2 = <'>>, ''(Msg, Bin2, TrUserData) end, [splice_trees('', MsgSizeBytes), replace_term('', gpb_lib:mk_fn(e_msg_, SubMsg))]); {yes, 0} -> %% special case, there will not be any e_msg_/2 function %% generated, so don't call it. gpb_codegen:format_fn( FnName, fun(_Msg, Bin, _TrUserData) -> <> end) end; possibly_format_mfield_encoder(MsgName, #?gpb_field{type={map,KType,VType}}=FieldDef, AnRes) -> MapAsMsgName = gpb_lib:map_type_to_msg_name(KType, VType), FieldDef2 = FieldDef#?gpb_field{type = {msg,MapAsMsgName}}, possibly_format_mfield_encoder(MsgName, FieldDef2, AnRes); possibly_format_mfield_encoder(MsgName, #?gpb_field{type={group,GroupName}, fnum=FNum}=FieldDef, _AnRes) -> FnName = mk_field_encode_fn_name(MsgName, FieldDef), EndTagBytes = key_to_binary_fields(FNum, group_end), gpb_codegen:format_fn( FnName, fun(Msg, Bin, TrUserData) -> GroupBin = ''(Msg, <<>>, TrUserData), <> end, [replace_term('', gpb_lib:mk_fn(e_msg_, GroupName)), splice_trees('EndTagBytes', EndTagBytes)]); possibly_format_mfield_encoder(_MsgName, _FieldDef, _Defs) -> []. is_msgsize_known_at_generationtime(MsgName, #anres{known_msg_size=MsgSizes}) -> case dict:fetch(MsgName, MsgSizes) of MsgSize when is_integer(MsgSize) -> {yes, MsgSize}; undefined -> no end. format_repeated_field_encoder2(MsgName, FDef, AnRes) -> #?gpb_field{fnum=FNum, type=Type, name=FName} = FDef, FnName = mk_field_encode_fn_name(MsgName, FDef), ElemEncoderFn = mk_field_encode_fn_name( MsgName, FDef#?gpb_field{occurrence=required}), KeyBytes = key_to_binary_fields(FNum, Type), ElemPath = [MsgName,FName,[]], Transl = gpb_gen_translators:find_translation(ElemPath, encode, AnRes), gpb_codegen:format_fn( FnName, fun([Elem | Rest], Bin, TrUserData) -> Bin2 = <'>>, Bin3 = ''('Tr'(Elem, TrUserData), Bin2, 'MaybeTrUserData'), call_self(Rest, Bin3, TrUserData); ([], Bin, _TrUserData) -> Bin end, [splice_trees('', KeyBytes), replace_term('', ElemEncoderFn), replace_term('Tr', Transl), splice_trees('MaybeTrUserData', gpb_gen_translators:maybe_userdata_param( FDef, ?expr(TrUserData)))]). format_packed_field_encoder2(MsgName, #?gpb_field{type=Type}=FDef, AnRes) -> case packed_byte_size_can_be_computed(Type) of {yes, BitLen, BitType} -> format_knownsize_packed_field_encoder2(MsgName, FDef, BitLen, BitType); no -> format_unknownsize_packed_field_encoder2(MsgName, FDef, AnRes) end. packed_byte_size_can_be_computed(fixed32) -> {yes, 32, [little]}; packed_byte_size_can_be_computed(sfixed32) -> {yes, 32, [little,signed]}; packed_byte_size_can_be_computed(float) -> {yes, 32, float}; packed_byte_size_can_be_computed(fixed64) -> {yes, 64, [little]}; packed_byte_size_can_be_computed(sfixed64) -> {yes, 64, [little,signed]}; packed_byte_size_can_be_computed(double) -> {yes, 64, double}; packed_byte_size_can_be_computed(_) -> no. format_knownsize_packed_field_encoder2(MsgName, #?gpb_field{name=FName, fnum=FNum}=FDef, BitLen, BitType) -> FnName = mk_field_encode_fn_name(MsgName, FDef), KeyBytes = key_to_binary_fields(FNum, bytes), PackedFnName = gpb_lib:mk_fn(e_pfield_, MsgName, FName), [gpb_codegen:format_fn( FnName, fun(Elems, Bin, _TrUserData) when Elems =/= [] -> Bin2 = <'>>, Bin3 = e_varint(length(Elems) * '', Bin2), ''(Elems, Bin3); ([], Bin, _TrUserData) -> Bin end, [splice_trees('', KeyBytes), replace_term('', BitLen div 8), replace_term('', PackedFnName)]), case BitType of float -> format_packed_float_encoder(PackedFnName); double -> format_packed_double_encoder(PackedFnName); _ -> gpb_codegen:format_fn( PackedFnName, fun([Value | Rest], Bin) -> Bin2 = <'/''>>, call_self(Rest, Bin2); ([], Bin) -> Bin end, [replace_term('', BitLen), splice_trees('', [erl_syntax:atom(T) || T <- BitType])]) end]. format_unknownsize_packed_field_encoder2(MsgName, #?gpb_field{name=FName, fnum=FNum}=FDef, AnRes) -> FnName = mk_field_encode_fn_name(MsgName, FDef), ElemEncoderFn = mk_field_encode_fn_name( MsgName, FDef#?gpb_field{occurrence=required}), KeyBytes = key_to_binary_fields(FNum, bytes), PackedFnName = gpb_lib:mk_fn(e_pfield_, MsgName, FName), ElemPath = [MsgName,FName,[]], Transl = gpb_gen_translators:find_translation(ElemPath, encode, AnRes), [gpb_codegen:format_fn( FnName, fun(Elems, Bin, TrUserData) when Elems =/= [] -> SubBin = ''(Elems, <<>>, TrUserData), Bin2 = <'>>, Bin3 = e_varint(byte_size(SubBin), Bin2), <>; ([], Bin, _TrUserData) -> Bin end, [splice_trees('', KeyBytes), replace_term('', PackedFnName)]), gpb_codegen:format_fn( PackedFnName, fun([Value | Rest], Bin, TrUserData) -> Bin2 = ''('Tr'(Value, TrUserData), Bin, 'MaybeTrUserData'), call_self(Rest, Bin2, TrUserData); ([], Bin, _TrUserData) -> Bin end, [replace_term('', ElemEncoderFn), replace_term('Tr', Transl), splice_trees('MaybeTrUserData', gpb_gen_translators:maybe_userdata_param( FDef, ?expr(TrUserData)))])]. format_type_encoders(AnRes) -> [format_varlength_field_encoders(AnRes), format_fixlength_field_encoders(AnRes), [format_varint_encoder() || is_varint_encoder_needed(AnRes)]]. format_varlength_field_encoders(#anres{used_types=UsedTypes}) -> IsUsedSint32 = gpb_lib:smember(sint32, UsedTypes), IsUsedSint64 = gpb_lib:smember(sint64, UsedTypes), IsUsedInt32 = gpb_lib:smember(int32, UsedTypes), IsUsedInt64 = gpb_lib:smember(int64, UsedTypes), IsUsedBool = gpb_lib:smember(bool, UsedTypes), IsUsedString = gpb_lib:smember(string, UsedTypes), IsUsedBytes = gpb_lib:smember(bytes, UsedTypes), [[format_sint_encoder() || IsUsedSint32 orelse IsUsedSint64], [format_int_encoder(int32, 32) || IsUsedInt32], [format_int_encoder(int64, 64) || IsUsedInt64], [format_bool_encoder() || IsUsedBool], [format_string_encoder() || IsUsedString], [format_bytes_encoder() || IsUsedBytes]]. format_fixlength_field_encoders(AnRes) -> NeedsFixed32 = needs_f_enc(fixed32, AnRes), NeedsSFixed32 = needs_f_enc(sfixed32, AnRes), NeedsFloat = needs_f_enc(float, AnRes), NeedsFixed64 = needs_f_enc(fixed64, AnRes), NeedsSFixed64 = needs_f_enc(sfixed64, AnRes), NeedsDouble = needs_f_enc(double, AnRes), [[format_fixed_encoder(fixed32, 32, [little]) || NeedsFixed32], [format_fixed_encoder(sfixed32, 32, [little,signed]) || NeedsSFixed32], [format_fixed_encoder(fixed64, 64, [little]) || NeedsFixed64], [format_fixed_encoder(sfixed64, 64, [little,signed]) || NeedsSFixed64], [format_float_encoder(float) || NeedsFloat], [format_double_encoder(double) || NeedsDouble]]. needs_f_enc(FixedType, #anres{used_types=UsedTypes, fixlen_types=FTypes}) -> %% If a fixlength-type occurs _only_ as a packed repeated field, %% we need not generate a special encoder-function for it case [FT || FT <- sets:to_list(FTypes), FT#ft.type == FixedType] of [#ft{occurrence=repeated, is_packed=true}] -> false; _ -> gpb_lib:smember(FixedType, UsedTypes) end. is_varint_encoder_needed(#anres{used_types=UsedTypes}=AnRes) -> TypesNeedingAVarintEncoder = [int32, int64, uint32, uint64, sint32, sint64, string, bytes], gpb_lib:smember_any(TypesNeedingAVarintEncoder, UsedTypes) orelse gpb_lib:any_enum_field_exists(UsedTypes) orelse gpb_lib:any_packed_field_exists(AnRes) orelse gpb_lib:at_least_one_submsg_with_size_not_known_at_compile_time_exists( AnRes). format_sint_encoder() -> gpb_codegen:format_fn( e_type_sint, fun(Value, Bin) when Value >= 0 -> e_varint(Value * 2, Bin); (Value, Bin) -> e_varint(Value * -2 - 1, Bin) end). format_int_encoder(Type, _BitLen) -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_type_, Type), fun(Value, Bin) when 0 =< Value, Value =< 127 -> <>; %% fast path (Value, Bin) -> %% Encode as a 64 bit value, for interop compatibility. %% Some implementations don't decode 32 bits properly, %% and Google's protobuf (C++) encodes as 64 bits <> = <>, e_varint(N, Bin) end, []). format_bool_encoder() -> gpb_codegen:format_fn( e_type_bool, fun(true, Bin) -> <>; (false, Bin) -> <>; (1, Bin) -> <>; (0, Bin) -> <> end). format_fixed_encoder(Type, BitLen, BitType) -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_type_, Type), fun(Value, Bin) -> <'/''>> end, [replace_term('', BitLen), splice_trees('', [erl_syntax:atom(T) || T <- BitType])]). format_packed_float_encoder(FnName) -> gpb_codegen:format_fn( FnName, fun([V | Rest], Bin) when is_number(V) -> call_self(Rest, <>); ([infinity | Rest], Bin) -> call_self(Rest, <>); (['-infinity' | Rest], Bin) -> call_self(Rest, <>); ([nan | Rest], Bin) -> call_self(Rest, <>); ([], Bin) -> Bin end, []). format_packed_double_encoder(FnName) -> gpb_codegen:format_fn( FnName, fun([V | Rest], Bin) when is_number(V) -> call_self(Rest, <>); ([infinity | Rest], Bin) -> call_self(Rest, <>); (['-infinity' | Rest], Bin) -> call_self(Rest, <>); ([nan | Rest], Bin) -> call_self(Rest, <>); ([], Bin) -> Bin end, []). format_float_encoder(Type) -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_type_, Type), fun(V, Bin) when is_number(V) -> <>; (infinity, Bin) -> <>; ('-infinity', Bin) -> <>; (nan, Bin) -> <> end, []). format_double_encoder(Type) -> gpb_codegen:format_fn( gpb_lib:mk_fn(e_type_, Type), fun(V, Bin) when is_number(V) -> <>; (infinity, Bin) -> <>; ('-infinity', Bin) -> <>; (nan, Bin) -> <> end, []). format_string_encoder() -> gpb_codegen:format_fn( e_type_string, fun(S, Bin) -> Utf8 = unicode:characters_to_binary(S), Bin2 = e_varint(byte_size(Utf8), Bin), <> end). format_bytes_encoder() -> gpb_codegen:format_fn( e_type_bytes, fun(Bytes, Bin) when is_binary(Bytes) -> Bin2 = e_varint(byte_size(Bytes), Bin), <>; (Bytes, Bin) when is_list(Bytes) -> BytesBin = iolist_to_binary(Bytes), Bin2 = e_varint(byte_size(BytesBin), Bin), <> end). format_varint_encoder() -> gpb_codegen:format_fn( e_varint, fun(N, Bin) when N =< 127 -> <>; (N, Bin) -> Bin2 = <>, call_self(N bsr 7, Bin2) end). enum_to_binary_fields(Value) -> %% Encode as a 64 bit value, for interop compatibility. %% Some implementations don't decode 32 bits properly, %% and Google's protobuf (C++) encodes as 64 bits <> = <>, gpb_lib:varint_to_binary_fields(N). key_to_binary_fields(FNum, {group,_}) -> key_to_binary_fields(FNum, group_start); key_to_binary_fields(FNum, Type) -> Key = (FNum bsl 3) bor gpb:encode_wiretype(Type), gpb_lib:varint_to_binary_fields(Key). format_is_empty_string(#anres{has_p3_opt_strings=false}) -> ""; format_is_empty_string(#anres{has_p3_opt_strings=true}) -> [gpb_codegen:format_fn( is_empty_string, fun("") -> true; (<<>>) -> true; (L) when is_list(L) -> not string_has_chars(L); (B) when is_binary(B) -> false end), gpb_codegen:format_fn( string_has_chars, fun([C | _]) when is_integer(C) -> true; % common case ([H | T]) -> case string_has_chars(H) of true -> true; false -> call_self(T) end; (B) when is_binary(B), byte_size(B) =/= 0 -> true; (C) when is_integer(C) -> true; (<<>>) -> false; ([]) -> false end)].