%%% 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 nif C++ code that calls C++ code generated by Google's %%% protobuf compiler (protoc). %%% %%% Here is also code for generating the load_nif/0 erlang function %%% that actually loads the nif C++ code. %%% %%% @private -module(gpb_gen_nif). -export([format_load_nif/2]). -export([format_nif_encoder_error_wrappers/3]). -export([format_nif_decoder_error_wrappers/3]). -export([format_nif_to_json_error_wrappers/3]). -export([format_nif_from_json_error_wrappers/3]). -export([format_nif_cc/4]). -include("../include/gpb.hrl"). -include("gpb_codegen.hrl"). -include("gpb_compile.hrl"). -import(gpb_lib, [replace_term/2]). -record(cc_enum, {type :: string(), enums :: [{atom(), string()}], unaliased_enums :: [{atom(), string()}]}). -record(cc_msg, {type :: string()}). format_load_nif(Mod, Opts) -> VsnAsList = gpb:version_as_list(), case proplists:get_value(load_nif, Opts, '$undefined') of '$undefined' -> ["load_nif() ->\n", %% Note: using ?MODULE here has impacts on compiling to %% binary, because we don't pass it through the preprocessor %% maybe we should? " SelfDir = filename:dirname(code:which(?MODULE)),\n", " NifDir = case lists:reverse(filename:split(SelfDir)) of\n" " [\"ebin\" | PDR] ->\n" " PD = filename:join(lists:reverse(PDR)),\n", " filename:join(PD, \"priv\");\n", " _ ->\n", " SelfDir\n", " end,\n", " NifBase = \"", atom_to_list(Mod) ++ ".nif", "\",\n", " Nif = filename:join(NifDir, NifBase),\n", ?f(" erlang:load_nif(Nif, ~w).\n", [VsnAsList])]; LoadNifFnText when is_list(LoadNifFnText); is_binary(LoadNifFnText) -> [replace_tilde_s(iolist_to_binary(LoadNifFnText), iolist_to_binary(?f("\"~s.nif\"", [Mod])), iolist_to_binary(?f("~w", [VsnAsList])))] end. replace_tilde_s(<<"{{nifbase}}", Rest/binary>>, ModBin, VsnBin) -> <>; replace_tilde_s(<<"{{loadinfo}}", Rest/binary>>, ModBin, VsnBin) -> <>; replace_tilde_s(<>, ModBin, VsnBin) -> <>; replace_tilde_s(<<>>, _ModBin, _VsnBin) -> <<>>. %% error wrappers for encoders format_nif_encoder_error_wrappers(Defs, _AnRes, _Opts) -> [format_msg_nif_encode_error_wrapper(MsgName) || {{msg, MsgName}, _MsgDef} <- Defs]. format_msg_nif_encode_error_wrapper(MsgName) -> gpb_codegen:format_fn( gpb_lib:mk_fn(encode_msg_, MsgName), fun(Msg) -> erlang:nif_error({error,{nif_not_loaded,''}}, [Msg]) end, [replace_term('', MsgName)]). %% error wrappers for decoders format_nif_decoder_error_wrappers(Defs, _AnRes, _Opts) -> [format_msg_nif_decode_error_wrapper(MsgName) || {{msg, MsgName}, _MsgDef} <- Defs]. format_msg_nif_decode_error_wrapper(MsgName) -> gpb_codegen:format_fn( gpb_lib:mk_fn(decode_msg_, MsgName), fun(Bin) -> erlang:nif_error({error,{nif_not_loaded,''}}, [Bin]) end, [replace_term('', MsgName)]). %% error wrappers for to_json format_nif_to_json_error_wrappers(Defs, _AnRes, _Opts) -> [format_msg_nif_to_json_error_wrapper(MsgName) || {{msg, MsgName}, _MsgDef} <- Defs]. format_msg_nif_to_json_error_wrapper(MsgName) -> gpb_codegen:format_fn( gpb_lib:mk_fn(to_json_msg_, MsgName), fun(Msg) -> erlang:nif_error({error,{nif_not_loaded,''}}, [Msg]) end, [replace_term('', MsgName)]). %% error wrappers for from_json format_nif_from_json_error_wrappers(Defs, _AnRes, _Opts) -> [format_msg_nif_from_json_error_wrapper(MsgName) || {{msg, MsgName}, _MsgDef} <- Defs]. format_msg_nif_from_json_error_wrapper(MsgName) -> gpb_codegen:format_fn( gpb_lib:mk_fn(from_json_msg_, MsgName), fun(JBin) -> erlang:nif_error({error,{nif_not_loaded,''}}, [JBin]) end, [replace_term('', MsgName)]). %% -- NIF C++ code --- format_nif_cc(Mod, Defs, AnRes, Opts) -> DoJson = gpb_lib:json_by_opts(Opts), CCMapping = calc_cc_mapping(Defs, AnRes, Opts), iolist_to_binary( [format_nif_cc_includes(Mod, Defs, AnRes, Opts), format_nif_cc_oneof_version_check_if_present(Defs), format_nif_cc_maptype_version_check_if_present(Defs), format_nif_cc_proto3_version_check_if_present(Defs), format_nif_cc_map_api_check_if_needed(Opts), format_nif_cc_json_api_check_if_needed(Opts), format_nif_cc_json_includes_if_needed(Opts), format_nif_cc_local_function_decls(Mod, Defs, CCMapping, Opts), format_nif_cc_mk_consts(Mod, Defs, AnRes, Opts), format_nif_cc_mk_atoms(Mod, Defs, AnRes, Opts), format_nif_cc_mk_is_key(Mod, Defs, AnRes, Opts), format_nif_cc_mk_mk_key(Mod, Defs, AnRes, Opts), format_nif_cc_utf8_conversion(Mod, Defs, AnRes, Opts), format_nif_cc_encoders(Mod, Defs, CCMapping, Opts), format_nif_cc_packers(Mod, Defs, CCMapping, Opts), format_nif_cc_decoders(Mod, Defs, CCMapping, Opts), format_nif_cc_unpackers(Mod, Defs, CCMapping, Opts), [[format_nif_cc_to_jsoners(Mod, Defs, CCMapping, Opts), format_nif_cc_from_jsoners(Mod, Defs, CCMapping, Opts)] || DoJson], format_nif_cc_foot(Mod, Defs, Opts)]). %% Create a mapping {msg,MsgName} -> Info = #cc_msg{} %% {enum,EnumName} -> Info = #cc_enum{} calc_cc_mapping(Defs, _AnRes, Opts) -> UsesPackage = proplists:get_bool(use_packages, Opts), %% FIXME: take any renaming options into consideration as well {CCMapping, _Pkg, _Prefix} = lists:foldl( fun({package, Package}, {Acc, _Pkg, _PrevPkgPrefix}) -> if UsesPackage -> %% Names are already prepended with package name, %% on dotted form {Acc, atom_to_list(Package), ""}; true -> CPkgPrefix = "::" ++ dot_replace_s(Package, "::"), {Acc, atom_to_list(Package), CPkgPrefix} end; ({{msg, MsgName}, _Fields}, {Acc, Pkg, CPrefix}) -> CCType = CPrefix ++ "::" ++ dot_replace_s(MsgName, "::"), Info = #cc_msg{type=CCType}, {[{MsgName, Info} | Acc], Pkg, CPrefix}; ({{group, GName}, _Fields}, {Acc, Pkg, CPrefix}) -> CCType = CPrefix ++ "::" ++ dot_replace_s(GName, "::"), Info = #cc_msg{type=CCType}, {[{GName, Info} | Acc], Pkg, CPrefix}; ({{enum, EnumName}, Enums}, {Acc, Pkg, CPrefix}) -> %% If we have this proto %% %% syntax="proto3"; %% package x.y; %% message msg1 { %% top_level_enum f1 = 1; %% msg_level_enum f2 = 2; %% enum msg_level_enum { ba = 0; bb = 1; } %% } %% enum top_level_enum { aa = 0; ab = 1; } %% %% then we refer to them as follows in C++ %% %% ::x:y::top_level_enum // the type name %% ::x:y::aa // an enum symbol %% ::x:y::msg1_msg_level_enum // the type name %% ::x:y::msg1_msg_level_enum_ba // an enum symbol %% %% The C++ enums in .pb.h are declared in the namespace %% but not inside any class. %% EnumPart = % eg: "top_level_enum" or "msg1.msg_level_enum" if UsesPackage -> split_enum_with_pkg(EnumName, Pkg); true -> atom_to_list(EnumName) end, CPkg = if Pkg /= '$undefined' -> "::" ++ dot_replace_s(Pkg, "::"); true -> "" end, CCType = (CPkg ++ "::" ++ dot_replace_s(EnumPart, "_")), EPrefix = case is_dotted(EnumPart) of false -> ""; true -> dot_replace_s(EnumPart, "_") ++ "_" end, MkCCSym = fun(Sym) -> ?ff("~s::~s~s", [CPkg, EPrefix, 'sym_to_c++'(Sym)]) end, CCEnums = [{Sym, MkCCSym(Sym)} || {Sym, _Val} <- Enums], UnaliasedEnums = gpb_lib:unalias_enum(Enums), UnaliasedCCEnums = [{Sym, MkCCSym(Sym)} || {Sym, _Val} <- UnaliasedEnums], Info = #cc_enum{type=CCType, enums=CCEnums, unaliased_enums=UnaliasedCCEnums}, {[{EnumName, Info} | Acc], Pkg, CPrefix}; (_Other, {Acc, Pkg, CPrefix}) -> {Acc, Pkg, CPrefix} end, {[], '$undefined', ""}, Defs), dict:from_list(CCMapping). split_enum_with_pkg(EnumName, Pkg) -> EnumStr = atom_to_list(EnumName), NameParts = gpb_lib:string_lexemes(EnumStr, "."), PkgParts = gpb_lib:string_lexemes(Pkg, "."), gpb_lib:dot_join(drop_prefix(PkgParts, NameParts)). drop_prefix([], Rest) -> Rest; drop_prefix([X | PrefixRest], [X | Rest]) -> drop_prefix(PrefixRest, Rest). is_lite_rt(Defs) -> OptimizeOpts = [Opt || {option,{optimize_for,Opt}} <- Defs], lists:any(fun(OptOpt) -> OptOpt == 'LITE_RUNTIME' end, OptimizeOpts). format_nif_cc_includes(Mod, Defs, AnRes, _Opts) -> IsLiteRT = is_lite_rt(Defs), ["#include \n", "#include \n", "\n", "#include \n", "\n", ?f("#include \"~s.pb.h\"\n", [Mod]), ["#include \n" || IsLiteRT], format_math_include(AnRes), "\n"]. format_math_include(AnRes) -> case is_any_field_of_type_float_or_double(AnRes) of true -> ["#include \n", "#include \n", "#ifndef isnan\n", "# define isnan std::isnan\n", "#endif\n", "\n", "#ifndef isinf\n", "# define isinf std::isinf\n", "#endif\n", "\n"]; false -> "" end. format_nif_cc_oneof_version_check_if_present(Defs) -> case contains_oneof(Defs) of true -> ["#if GOOGLE_PROTOBUF_VERSION < 2006000\n" "#error \"The proto definitions contain 'oneof' fields.\"\n" "#error \"This feature appeared in protobuf 2.6.0, but\"\n" "#error \"it appears your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n" "\n"]; false -> "" end. contains_oneof([{{msg,_}, Fields} | Rest]) -> case lists:any(fun(F) -> is_record(F, gpb_oneof) end, Fields) of false -> contains_oneof(Rest); true -> true end; contains_oneof([_ | Rest]) -> contains_oneof(Rest); contains_oneof([]) -> false. format_nif_cc_maptype_version_check_if_present(Defs) -> case contains_maptype_field(Defs) of true -> ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n" "#error \"The proto definitions contain 'map' fields.\"\n" "#error \"This feature appeared in protobuf 3, but\"\n" "#error \"it appears your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n" "\n"]; false -> "" end. contains_maptype_field([{{msg,_}, Fields} | Rest]) -> case lists:any(fun is_maptype_field/1, Fields) of false -> contains_maptype_field(Rest); true -> true end; contains_maptype_field([_ | Rest]) -> contains_maptype_field(Rest); contains_maptype_field([]) -> false. is_maptype_field(#?gpb_field{type={map,_,_}}) -> true; is_maptype_field(_) -> false. format_nif_cc_proto3_version_check_if_present(Defs) -> case proplists:get_value(syntax, Defs) of "proto3" -> ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n" "#error \"The proto definitions use 'proto3' syntax.\"\n" "#error \"This feature appeared in protobuf 3, but\"\n" "#error \"it appears your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n" "\n"]; _ -> "" end. format_nif_cc_map_api_check_if_needed(Opts) -> case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of '2tuples' -> ""; maps -> %% The maps api functions appeared in erl_nif.h version 2.6, %% which is Erlang 17, but they were not documented until 18.0. %% There were some changes to the iterators in 2.8 (= Erlang 18.0) %% but those are not needed. ["#if (!(", format_nif_check_version_or_later(2, 6), "))\n" "#error \"Maps was specified. The needed nif interface for\"\n" "#error \"maps appeared in version 2.6 (Erlang 17), but\"\n" "#error \"it appears your erl_nif version is older. Please\"\n" "#error \"update Erlang.\"\n" "#endif\n" "\n"] end. format_nif_cc_json_api_check_if_needed(Opts) -> case gpb_lib:json_by_opts(Opts) of true -> PreserveFNames = proplists:get_bool(json_preserve_proto_field_names, Opts), CaseInsensitiveEnums = proplists:get_value(json_case_insensitive_enum_parsing, Opts), ["#if GOOGLE_PROTOBUF_VERSION < 3000000\n" "#error \"The json option was specified,\"\n" "#error \"this feature appeared in protobuf 3, but\"\n" "#error \"it appears your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n", if PreserveFNames -> ["\n" "#if GOOGLE_PROTOBUF_VERSION < 3003000\n" "#error \"The json_preserve_proto_field_names option\"\n" "#error \"was set, but protobuf support for this\"\n" "#error \"first appeared in protobuf 3.3.0, and\"\n" "#error \"it appears your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n"]; true -> "" end, if CaseInsensitiveEnums == true; CaseInsensitiveEnums == undefined -> % default is true ""; CaseInsensitiveEnums == false -> ["\n" "#if GOOGLE_PROTOBUF_VERSION < 3007000\n" "#error \"The json_case_insensitive_enum_parsing\"\n" "#error \"option was set to false, but support \"\n" "#error \"for configuring this first appeared\"\n" "#error \"in version 3.7.0, and it appears \"\n" "#error \"your protobuf is older. Please\"\n" "#error \"update protobuf.\"\n" "#endif\n"] end]; false -> "" end. format_nif_cc_json_includes_if_needed(Opts) -> case gpb_lib:json_by_opts(Opts) of true -> ["#include \n"]; false -> "" end. format_nif_cc_local_function_decls(_Mod, Defs, CCMapping, _Opts) -> [[begin PackFnName = mk_c_fn(p_msg_, MsgName), UnpackFnName = mk_c_fn(u_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), [["static int ",PackFnName,["(ErlNifEnv *env, ", "const ERL_NIF_TERM r,", CMsgType," *m);\n"]], ["static ERL_NIF_TERM ",UnpackFnName,["(ErlNifEnv *env, ", "const ",CMsgType," *m);\n"]]] end || {{msg, MsgName}, _Fields} <- Defs], "\n"]. format_nif_cc_mk_atoms(_Mod, Defs, AnRes, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts), %% About gpb_aa_<...> vs gpb_fa_<...> C variables %% ---------------------------------------------- %% gpb_aa_<...>: %% Things that are used as atoms will be in C variables gpb_aa_<...> %% These are: %% * constants: true, false, undefined, infinity, '-infinity', nan %% * message names (record names)a %% * enum symbols %% * oneof tags when _not_ flat oneofs %% %% gpb_fa_<...> %% Map fields names can be either atoms or binaries, depending on %% the maps_key_type option. Additionally, with maps_oneof = flat, the %% oneof tags will be map keys. Things that will be map keys, %% are stored in C variables gpb_fa_<...>. These are either %% atoms or C strings (char arrays) used for constructing and comparing %% binaries (the is_key and mk_key functions). %% Here are: %% * map key field names, ie names of #?gpb_field{}s %% * #gpb_oneof{} field names when _not_ flat oneofs (avoid unused vars) %% * oneof tags when flat oneofs {AtomVars, FieldNameVars} = calc_atoms_fields(Defs, AnRes, Opts), [[?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- AtomVars], if Maps, MapsKeyType == binary -> [?f("static const char *~s = \"~s\";\n", [Var, Atom]) || {Var,Atom} <- FieldNameVars]; true -> [?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- FieldNameVars] end, "\n", ["static void install_atoms(ErlNifEnv *env)\n" "{\n", [?f(" ~s = enif_make_atom(env, \"~s\");\n", [Var, Atom]) || {Var, Atom} <- AtomVars], if Maps, MapsKeyType == binary -> ""; true -> [?f(" ~s = enif_make_atom(env, \"~s\");\n", [Var, Atom]) || {Var, Atom} <- FieldNameVars] end, "}\n", "\n"]]. calc_atoms_fields(Defs, AnRes, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, MappingUnset = gpb_lib:get_mapping_and_unset_by_opts(Opts), FlatMaps = case MappingUnset of #maps{unset_optional=omitted, oneof=flat} -> true; _ -> false end, %% Calculate things that are to be always atoms Atoms0 = % deep list of atoms [[collect_record_names(Defs) || MappingUnset == records], collect_constants(AnRes), collect_enum_syms(Defs), [collect_oneof_tags(Defs) || not FlatMaps]], Atoms1 = deep_list_to_unqiue_list(Atoms0), AtomVars0 = [{mk_c_var(gpb_aa_, minus_to_m(A)), A} || A <- Atoms1], NoValue = case MappingUnset of records -> undefined; #maps{unset_optional=present_undefined} -> undefined; #maps{unset_optional=omitted} -> '$undef' end, AtomVars1 = [{"gpb_x_no_value", NoValue} | AtomVars0], %% Calculate map fields names (atoms or eventually binaries depending on %% option) FieldNames0 = % deep list of atoms [[collect_toplevel_non_oneof_field_names(Defs) || Maps], [collect_oneof_names(Defs) || Maps, not FlatMaps], [collect_oneof_tags(Defs) || FlatMaps]], FieldNames = deep_list_to_unqiue_list(FieldNames0), FieldNameVars = [{mk_c_var(gpb_fa_, minus_to_m(A)), A} || A <- FieldNames], {AtomVars1, FieldNameVars}. collect_record_names(Defs) -> [MsgName || {_, MsgName, _Fields} <- gpb_lib:msgs_or_groups(Defs)]. collect_enum_syms(Defs) -> [[Sym || {Sym, _V} <- EnumDef] || {{enum, _}, EnumDef} <- Defs]. collect_constants(AnRes) -> [case is_any_field_of_type_enum(AnRes) of true -> [undefined]; false -> [] end, case is_any_field_of_type_bool(AnRes) of true -> [true, false]; false -> [] end, case is_any_field_of_type_float_or_double(AnRes) of true -> [infinity, '-infinity', nan]; false -> [] end]. collect_oneof_tags(Defs) -> [[[Tag || #?gpb_field{name=Tag} <- OFields] || #gpb_oneof{fields=OFields} <- Fields] || {_msg_or_group, _, Fields} <- gpb_lib:msgs_or_groups(Defs)]. collect_toplevel_non_oneof_field_names(Defs) -> [[FName || #?gpb_field{name=FName} <- Fields] || {_, _Name, Fields} <- gpb_lib:msgs_or_groups(Defs)]. collect_oneof_names(Defs) -> [[FName || #gpb_oneof{name=FName} <- Fields] || {_, _Name, Fields} <- gpb_lib:msgs_or_groups(Defs)]. deep_list_to_unqiue_list(DL) -> sets:to_list(deep_get_unique_aux(DL, sets:new())). deep_get_unique_aux(L, InitAcc) when is_list(L) -> lists:foldl(fun deep_get_unique_aux/2, InitAcc, L); deep_get_unique_aux(Elem, Acc) -> sets:add_element(Elem, Acc). minus_to_m(A) -> case atom_to_list(A) of "-"++Rest -> "m"++Rest; _ -> A end. format_nif_cc_mk_consts(_Mod, _Defs, AnRes, _Opts) -> case is_any_field_of_type_bool(AnRes) of true -> ["static ERL_NIF_TERM gpb_true_int;\n" "static void install_consts(ErlNifEnv *env)\n" "{\n", " gpb_true_int = enif_make_uint(env, 1);\n" "}\n"]; _ -> ["static void install_consts(ErlNifEnv *env)\n" "{\n", "}\n"] end. format_nif_cc_mk_is_key(_Mod, _Defs, _AnRes, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts), if Maps, MapsKeyType == binary -> ["static int is_key(ErlNifEnv *env,\n" " const ERL_NIF_TERM x,\n" " const char *key_to_check_for)\n", "{\n", " ErlNifBinary xb;\n" " size_t key_len;\n" " if (!enif_inspect_binary(env, x, &xb))\n" " return 0;\n" " key_len = strlen(key_to_check_for);\n" " return (xb.size == key_len &&\n" " memcmp(xb.data, key_to_check_for, key_len) == 0);\n" "}\n"]; Maps -> ["static int is_key(ErlNifEnv *env,\n" " const ERL_NIF_TERM x,\n" " const ERL_NIF_TERM key_to_check_for)\n", "{\n", " return enif_is_identical(x, key_to_check_for);\n" "}\n"]; true -> "" end. format_nif_cc_mk_mk_key(_Mod, _Defs, _AnRes, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, MapsKeyType = gpb_lib:get_maps_key_type_by_opts(Opts), if Maps, MapsKeyType == binary -> ["static ERL_NIF_TERM mk_key(ErlNifEnv *env,\n" " const char *key)\n", "{\n", " ERL_NIF_TERM b;\n" " unsigned char *data;\n" " size_t len;\n" "\n" " len = strlen(key);\n" " data = enif_make_new_binary(env, len, &b);\n" " memmove(data, key, len);\n" " return b;\n" "}\n"]; Maps -> ["static ERL_NIF_TERM mk_key(ErlNifEnv *env,\n" " const ERL_NIF_TERM key)\n", "{\n", " return key;\n" "}\n"]; true -> "" end. format_nif_cc_utf8_conversion(_Mod, _Defs, AnRes, Opts) -> case is_any_field_of_type_string(AnRes) of true -> format_nif_cc_utf8_conversion_code(Opts); false -> "" end. is_any_field_of_type_string(#anres{used_types=UsedTypes}) -> sets:is_element(string, UsedTypes). is_any_field_of_type_enum(#anres{used_types=UsedTypes}) -> sets:fold(fun({enum,_}, _) -> true; (_, Acc) -> Acc end, false, UsedTypes). is_any_field_of_type_bool(#anres{used_types=UsedTypes}) -> sets:is_element(bool, UsedTypes). is_any_field_of_type_float_or_double(#anres{used_types=UsedTypes}) -> sets:is_element(float, UsedTypes) orelse sets:is_element(double, UsedTypes). format_nif_cc_utf8_conversion_code(Opts) -> [case gpb_lib:get_strings_as_binaries_by_opts(Opts) of true -> ["static ERL_NIF_TERM\n", "utf8_to_erl_string(ErlNifEnv *env,\n", " const char *utf8data,\n", " unsigned int numOctets)\n" "{\n", " ERL_NIF_TERM b;\n", " unsigned char *data;\n", "\n", " data = enif_make_new_binary(env, numOctets, &b);\n", " memmove(data, utf8data, numOctets);\n", " return b;\n", "}\n"]; false -> ["/* Source for info is https://www.ietf.org/rfc/rfc2279.txt */\n", "\n", "static int\n", "utf8_count_codepoints(const char *sinit, int len)\n", "{\n", " int n = 0;\n", " const unsigned char *s0 = (unsigned char *)sinit;\n", " const unsigned char *s = s0;\n", "\n", " while ((s - s0) < len)\n", " {\n", " if (*s <= 0x7f) { n++; s++; } /* code point fits 1 octet */\n", " else if (*s <= 0xdf) { n++; s += 2; } /* 2 octets */\n", " else if (*s <= 0xef) { n++; s += 3; } /* 3 octets */\n", " else if (*s <= 0xf7) { n++; s += 4; }\n", " else if (*s <= 0xfb) { n++; s += 5; }\n", " else if (*s <= 0xfd) { n++; s += 6; }\n", " else return -1;\n", "\n", " if ((s - s0) > len)\n", " return -1;\n", " }\n", " return n;\n", "}\n", "\n", "static int\n", "utf8_to_uint32(unsigned int *dest, const char *src,\n", " int numCodePoints)\n", "{\n", " int i;\n", " const unsigned char *s = (unsigned char *)src;\n", "\n", "\n", " /* Should perhaps check for illegal chars in d800-dfff and\n", " * other illegal chars\n", " */\n", "\n", " for (i = 0; i < numCodePoints; i++)\n", " {\n", " if (*s <= 0x7f)\n", " *dest++ = *s++;\n", " else if (*s <= 0xdf) /* code point is 2 octets long */\n", " {\n", " *dest = *s++ & 0x1f; *dest <<= 6;\n", " *dest++ |= *s++ & 0x3f;\n", " }\n", " else if (*s <= 0xef) /* code point is 3 octets long */\n", " {\n", " *dest = *s++ & 0x0f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest++ |= *s++ & 0x3f;\n", " }\n", " else if (*s <= 0xf7) /* code point is 4 octets long */\n", " {\n", " *dest = *s++ & 0x07; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest++ |= *s++ & 0x3f;\n", " }\n", " else if (*s <= 0xfb) /* code point is 5 octets long */\n", " {\n", " *dest = *s++ & 0x03; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest++ |= *s++ & 0x3f;\n", " }\n", " else if (*s <= 0xfd) /* code point is 6 octets long */\n", " {\n", " *dest = *s++ & 0x01; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest |= *s++ & 0x3f; *dest <<= 6;\n", " *dest++ |= *s++ & 0x3f;\n", " }\n", " else\n", " return 0;\n", " }\n", " return 1;\n", "}\n", "\n", "static ERL_NIF_TERM\n", "utf8_to_erl_string(ErlNifEnv *env,\n", " const char *utf8data,\n", " unsigned int numOctets)\n", "{\n", " int numcp = utf8_count_codepoints(utf8data, numOctets);\n", "\n", " if (numcp < 0)\n", " {\n", " return enif_make_string(env,\n", " \"\",\n", " ERL_NIF_LATIN1);\n", " }\n", " else\n", " {\n", " unsigned int cp[numcp];\n", " ERL_NIF_TERM es[numcp];\n", " int i;\n", "\n", " utf8_to_uint32(cp, utf8data, numcp);\n", " for (i = 0; i < numcp; i++)\n", " es[i] = enif_make_uint(env, cp[i]);\n", " return enif_make_list_from_array(env, es, numcp);\n" " }\n", "}\n"] end, "\n", case gpb_lib:get_strings_as_binaries_by_opts(Opts) of true -> ""; false -> ["static int\n", "utf8_count_octets(ErlNifEnv *env, ERL_NIF_TERM str)\n", "{\n", " int n = 0;\n", "\n", " while (!enif_is_empty_list(env, str))\n", " {\n", " ERL_NIF_TERM head, tail;\n", " unsigned int c;\n", "\n", " if (!enif_get_list_cell(env, str, &head, &tail))\n", " return -1;\n", " if (!enif_get_uint(env, head, &c))\n", " return -1;\n", "\n", " if (c <= 0x7f) n += 1;\n", " else if (c <= 0x7ff) n += 2;\n", " else if (c <= 0xffff) n += 3;\n", " else if (c <= 0x1Fffff) n += 4;\n", " else if (c <= 0x3FFffff) n += 5;\n", " else if (c <= 0x7FFFffff) n += 6;\n", " else return -1;\n", "\n", " str = tail;\n", " }\n", " return n;\n", "}\n", "\n", "static int\n", "utf8_to_octets(ErlNifEnv *env, ERL_NIF_TERM str, char *dest)\n", "{\n", " unsigned char *s = (unsigned char *)dest;\n", "\n", " while (!enif_is_empty_list(env, str))\n", " {\n", " ERL_NIF_TERM head, tail;\n", " unsigned int c;\n", "\n", " if (!enif_get_list_cell(env, str, &head, &tail))\n", " return -1;\n", " if (!enif_get_uint(env, head, &c))\n", " return -1;\n", "\n", " if (c <= 0x7f)\n", " *s++ = c;\n", " else if (c <= 0x7ff)\n", " {\n", " *s++ = 0xc0 | (c >> 6);\n", " *s++ = 0x80 | (c & 0x3f);\n", " }\n", " else if (c <= 0xffff)\n", " {\n", " *s++ = 0xe0 | (c >> 12);\n", " *s++ = 0x80 | ((c >> 6) & 0x3f);\n", " *s++ = 0x80 | (c & 0x3f);\n", " }\n", " else if (c <= 0x1Fffff)\n", " {\n", " *s++ = 0xf0 | (c >> 18);\n", " *s++ = 0x80 | ((c >> 12) & 0x3f);\n", " *s++ = 0x80 | ((c >> 6) & 0x3f);\n", " *s++ = 0x80 | (c & 0x3f);\n", " }\n", " else if (c <= 0x3FFffff)\n", " {\n", " *s++ = 0xf0 | (c >> 24);\n", " *s++ = 0x80 | ((c >> 18) & 0x3f);\n", " *s++ = 0x80 | ((c >> 12) & 0x3f);\n", " *s++ = 0x80 | ((c >> 6) & 0x3f);\n", " *s++ = 0x80 | (c & 0x3f);\n", " }\n", " else if (c <= 0x7FFFffff)\n", " {\n", " *s++ = 0xf0 | (c >> 30);\n", " *s++ = 0x80 | ((c >> 24) & 0x3f);\n", " *s++ = 0x80 | ((c >> 18) & 0x3f);\n", " *s++ = 0x80 | ((c >> 12) & 0x3f);\n", " *s++ = 0x80 | ((c >> 6) & 0x3f);\n", " *s++ = 0x80 | (c & 0x3f);\n", " }\n", " else\n", " return 0;\n", "\n", " str = tail;\n", " }\n", " return 1;\n" "}\n"] end, "\n"]. format_nif_cc_foot(Mod, Defs, Opts) -> ["static int\n", "load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n", "{\n", " install_consts(env);\n" " install_atoms(env);\n" " return 0;\n", "}\n", "\n", "static int\n", "reload(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)\n", "{\n", " return 0;\n", "}\n", "\n", "void\n", "unload(ErlNifEnv *env, void *priv_data)\n", "{\n", "}\n", "\n", "static int\n", "upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data,\n", " ERL_NIF_TERM load_info)\n", "{\n", " return 0;\n", "}\n", "\n", "static ErlNifFunc nif_funcs[] =\n", "{\n", %% Dirty schedulers flags appeared in Erlang 17.3 = enif 2.7 %% but only if Erlang was configured with --enable-dirty-schedulers "#if ", format_nif_check_version_or_later(2, 7), "\n" "#ifdef ERL_NIF_DIRTY_SCHEDULER_SUPPORT\n", format_nif_cc_nif_funcs_list(Defs, "ERL_NIF_DIRTY_JOB_CPU_BOUND, ", Opts), "#else /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n", format_nif_cc_nif_funcs_list(Defs, "", Opts), "#endif /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n", "#else /* before 2.7 or 17.3 */\n", format_nif_cc_nif_funcs_list(Defs, no_flags, Opts), "#endif /* before 2.7 or 17.3 */\n" "};\n", "\n", ?f("ERL_NIF_INIT(~s, nif_funcs, load, reload, upgrade, unload)\n", [Mod])]. format_nif_check_version_or_later(Major, Minor) -> ?f("ERL_NIF_MAJOR_VERSION > ~w" " || " "(ERL_NIF_MAJOR_VERSION == ~w && ERL_NIF_MINOR_VERSION >= ~w)", [Major, Major, Minor]). format_nif_cc_nif_funcs_list(Defs, Flags, Opts) -> DoJson = gpb_lib:json_by_opts(Opts), MsgNames = [MsgName || {{msg, MsgName}, _MsgFields} <- Defs], FlagStr = if Flags == no_flags -> ""; true -> ", " ++ Flags end, [begin EncodeFnName = gpb_lib:mk_fn(encode_msg_, MsgName), EncodeCFnName = mk_c_fn(encode_msg_, MsgName), DecodeFnName = gpb_lib:mk_fn(decode_msg_, MsgName), DecodeCFnName = mk_c_fn(decode_msg_, MsgName), ToJsonFnName = gpb_lib:mk_fn(to_json_msg_, MsgName), ToJsonCFnName = mk_c_fn(to_json_msg_, MsgName), FromJsonFnName = gpb_lib:mk_fn(from_json_msg_, MsgName), FromJsonCFnName = mk_c_fn(from_json_msg_, MsgName), IsLast = I == length(MsgNames), Comma = ["," || not IsLast], DComma = if DoJson -> ","; not DoJson -> Comma end, [?f( " {\"~s\", 1, ~s~s},\n", [EncodeFnName, EncodeCFnName, FlagStr]), ?f( " {\"~s\", 1, ~s~s}~s\n", [DecodeFnName, DecodeCFnName, FlagStr, DComma]), [[?f(" {\"~s\", 1, ~s~s},\n", [ToJsonFnName, ToJsonCFnName, FlagStr]), ?f(" {\"~s\", 1, ~s~s}~s\n", [FromJsonFnName, FromJsonCFnName, FlagStr, Comma])] || DoJson]] end || {I, MsgName} <- gpb_lib:index_seq(MsgNames)]. format_nif_cc_encoders(Mod, Defs, CCMapping, Opts) -> [format_nif_cc_encoder(Mod, MsgName, Fields, CCMapping, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_encoder(_Mod, MsgName, _Fields, CCMapping, _Opts) -> FnName = mk_c_fn(encode_msg_, MsgName), PackFnName = mk_c_fn(p_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), ["static ERL_NIF_TERM\n", FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n", "{\n", " ErlNifBinary data;\n", " int byteSize;\n", " ",CMsgType," *m = new ",CMsgType,"();\n\n", "" " if (argc != 1)\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (m == NULL)\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (!",PackFnName,"(env, argv[0], m))\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " byteSize = m->ByteSize();\n" " if (!enif_alloc_binary(byteSize, &data))\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (!m->SerializeToArray(data.data, byteSize))\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " delete m;\n" " return enif_make_binary(env, &data);\n" "}\n" "\n"]. format_nif_cc_packers(_Mod, Defs, CCMapping, Opts) -> [format_nif_cc_packer(MsgName, Fields, Defs, CCMapping, Opts) || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)]. format_nif_cc_packer(MsgName, MsgFields, Defs, CCMapping, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, Mapping = gpb_lib:get_mapping_and_unset_by_opts(Opts), Fields = case Mapping of #maps{unset_optional=omitted, oneof=flat} -> gpb_lib:flatten_oneof_fields(MsgFields); _ -> MsgFields end, PackFnName = mk_c_fn(p_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), ["static int\n", PackFnName,["(ErlNifEnv *env, ", "const ERL_NIF_TERM r,", " ",CMsgType," *m)\n"], "{\n", if Maps -> NFieldsPlus1 = integer_to_list(length(MsgFields)+1), [" ERL_NIF_TERM k, v;\n", " ErlNifMapIterator iter;\n", " ERL_NIF_TERM elem[",NFieldsPlus1,"];\n", " ErlNifMapIteratorEntry first;\n", " int i;\n\n", "", initialize_map_iterator(4, "first"), " for (i = 1; i < ",NFieldsPlus1,"; i++)\n", " elem[i] = gpb_x_no_value;\n\n", "" " if (!enif_map_iterator_create(env, r, &iter, first))\n", " return 0;\n\n", "" " while (enif_map_iterator_get_pair(env, &iter, &k, &v))\n", " {\n", gpb_lib:split_indent_iolist( 8, [begin ElemIndex = gpb_lib:get_field_rnum(Field)-1, SrcVar = ?f("elem[~w]",[ElemIndex]), ?f("~sif (is_key(env, k, ~s))\n" "{\n" " elem[~w] = v;\n" "~s\n" "}\n", [if I == 1 -> ""; I > 1 -> "else " end, mk_c_var(gpb_fa_, gpb_lib:get_field_name(Field)), ElemIndex, gpb_lib:split_indent_iolist( 4, format_nif_cc_field_packer( SrcVar, "m", case Mapping of #maps{unset_optional=omitted} -> %% No need anymore to check for presence %% int the field packer. We've done that. optional_to_mandatory(Field); #maps{unset_optional=present_undefined} -> Field end, Defs, CCMapping, Opts))]) end || {I, Field} <- gpb_lib:index_seq(Fields)]), " enif_map_iterator_next(env, &iter);\n", " }\n", " enif_map_iterator_destroy(env, &iter);\n", "\n"]; not Maps -> [" int arity;\n" " const ERL_NIF_TERM *elem;\n\n" "" " if (!enif_get_tuple(env, r, &arity, &elem))\n" " return 0;\n" "\n", ?f(" if (arity != ~w)\n" " return 0;\n", [length(Fields) + 1]), "\n", [begin SrcVar = ?f("elem[~w]",[I]), gpb_lib:split_indent_iolist( 4, format_nif_cc_field_packer(SrcVar, "m", Field, Defs, CCMapping, Opts)) end || {I, Field} <- gpb_lib:index_seq(Fields)]] end, "\n" " return 1;\n" "}\n", "\n"]. optional_to_mandatory(#?gpb_field{occurrence=Occurrence}=Field) -> case Occurrence of optional -> Field#?gpb_field{occurrence=required}; required -> Field; repeated -> Field end; optional_to_mandatory(#gpb_oneof{}=Field) -> Field. format_nif_cc_field_packer(SrcVar, MsgVar, #?gpb_field{}=Field, Defs, CCMapping, Opts) -> #?gpb_field{occurrence=Occurrence, type=Type}=Field, case Occurrence of required -> format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, CCMapping, Opts, set); optional -> format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field, Defs, CCMapping, Opts); repeated -> case Type of {map,_,_} -> format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field, Defs, CCMapping, Opts); _ -> format_nif_cc_field_packer_repeated(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) end end; format_nif_cc_field_packer(SrcVar, MsgVar, #gpb_oneof{}=Field, Defs, CCMapping, Opts) -> #gpb_oneof{fields=OFields} = Field, [?f("if (!enif_is_identical(~s, gpb_x_no_value))~n" "{~n" " int oarity;~n" " const ERL_NIF_TERM *oelem;~n~n" "" " if (!enif_get_tuple(env, ~s, &oarity, &oelem) || oarity != 2)~n" " return 0;~n~n" "" " ~s~n" "}~n", [SrcVar, SrcVar, gpb_lib:split_indent_butfirst_iolist( 4, format_nif_cc_oneof_packer("oelem[0]", "oelem[1]", MsgVar, OFields, Defs, CCMapping, Opts))]), "\n"]. format_nif_cc_oneof_packer(NameVar, SrcVar, MsgVar, OFields, Defs, CCMapping, Opts) -> [[begin Else = if I == 1 -> ""; I > 1 -> "else " end, AtomVar = mk_c_var(gpb_aa_, Name), [?f("~sif (enif_is_identical(~s, ~s))~n", [Else, NameVar, AtomVar]), split_indent_iolist_unless_curly_block( 4, format_nif_cc_field_packer_single(SrcVar, MsgVar, OField, Defs, CCMapping, Opts, set))] end || {I, #?gpb_field{name=Name}=OField} <- gpb_lib:index_seq(OFields)], "else\n" " return 0;\n"]. format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) -> [?f("if (!enif_is_identical(~s, gpb_x_no_value))\n", [SrcVar]), format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, CCMapping, Opts, set)]. format_nif_cc_field_packer_single(SrcVar, MsgVar, #?gpb_field{type={group,Name}}=Field, Defs, CCMapping, Opts, Setter) -> format_nif_cc_field_packer_single( SrcVar, MsgVar, Field#?gpb_field{type={msg,Name}}, Defs, CCMapping, Opts, Setter); format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, CCMapping, Opts, Setter) -> #?gpb_field{name=FName, type=FType} = Field, CxxFName = 'field_name_to_c++'(FName), SetFn = fun(Exprs) -> case Setter of set -> ?f("~s->set_~s(~s);", [MsgVar, CxxFName, gpb_lib:comma_join(Exprs)]); add -> ?f("~s->add_~s(~s);", [MsgVar, CxxFName, gpb_lib:comma_join(Exprs)]); {set_var, V} -> case Exprs of [Val] -> ?f("~s = ~s;", [V, Val]); [S,N] -> ?f("~s.assign(~s, ~s);", [V, S, N]) end end end, case FType of float -> ?f("{\n" " double v;\n" " if (enif_is_identical(~s, gpb_aa_infinity))\n" " v = INFINITY;\n" " else if (enif_is_identical(~s, gpb_aa_minfinity))\n" " v = -INFINITY;\n" " else if (enif_is_identical(~s, gpb_aa_nan))\n" " v = NAN;\n" " else if (!enif_get_double(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SrcVar, SrcVar, SrcVar, SetFn(["(float)v"])]); double -> ?f("{\n" " double v;\n" " if (enif_is_identical(~s, gpb_aa_infinity))\n" " v = INFINITY;\n" " else if (enif_is_identical(~s, gpb_aa_minfinity))\n" " v = -INFINITY;\n" " else if (enif_is_identical(~s, gpb_aa_nan))\n" " v = NAN;\n" " else if (!enif_get_double(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SrcVar, SrcVar, SrcVar, SetFn(["v"])]); _S32 when FType == sint32; FType == int32; FType == sfixed32 -> ?f("{\n" " int v;\n" " if (!enif_get_int(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SetFn(["v"])]); _S64 when FType == sint64; FType == int64; FType == sfixed64 -> ?f("{\n" " ErlNifSInt64 v;\n" " if (!enif_get_int64(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SetFn(["v"])]); _U32 when FType == uint32; FType == fixed32 -> ?f("{\n" " unsigned int v;\n" " if (!enif_get_uint(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SetFn(["v"])]); _U64 when FType == uint64; FType == fixed64 -> ?f("{\n" " ErlNifUInt64 v;\n" " if (!enif_get_uint64(env, ~s, &v))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SetFn(["v"])]); bool -> ?f("{\n" " if (enif_is_identical(~s, gpb_aa_true))\n" " ~s\n" " else if (enif_is_identical(~s, gpb_true_int))\n" " ~s\n" " else\n" " ~s\n" "}\n", [SrcVar, SetFn(["1"]), SrcVar, SetFn(["1"]), SetFn(["0"])]); {enum, EnumName} -> #cc_enum{type=EType, enums=CCEnums} = dict:fetch(EnumName, CCMapping), ["{\n", ?f(" int v;\n" " if (enif_get_int(env, ~s, &v))\n" " ~s\n", [SrcVar, SetFn([?f("(~s)v", [EType])])]), [?f(" else if (enif_is_identical(~s, ~s))\n" " ~s\n", [SrcVar, mk_c_var(gpb_aa_, Sym), SetFn([CCSym])]) || {Sym, CCSym} <- CCEnums], " else\n" " return 0;\n" "}\n"]; string -> case gpb_lib:get_strings_as_binaries_by_opts(Opts) of true -> ?f("{\n" " ErlNifBinary b;\n" " if (!enif_inspect_binary(env, ~s, &b))\n" " return 0;\n" " ~s\n" "}\n", [SrcVar, SetFn(["reinterpret_cast(b.data)", "b.size"])]); false -> ?f("{\n" " size_t num_octs = utf8_count_octets(env, ~s);\n" "\n" " if (num_octs < 0)\n" " return 0;\n" " else\n" " {\n" " char s[num_octs];\n" " utf8_to_octets(env, ~s, s);\n" " ~s\n" " }\n" "}\n", [SrcVar, SrcVar, SetFn(["s", "num_octs"])]) end; bytes -> ?f("{\n" " ErlNifBinary b;\n" " if (enif_inspect_binary(env, ~s, &b)) {\n" " ~s\n" " } else if (enif_is_list(env, ~s)) {\n" " if (enif_inspect_iolist_as_binary(env, ~s, &b)) {\n" " ~s\n" " } else {\n" " return 0;\n" " }\n" " } else {\n" " return 0;\n" " }\n" "}\n", [SrcVar, SetFn(["reinterpret_cast(b.data)", "b.size"]), SrcVar, SrcVar, SetFn(["reinterpret_cast(b.data)", "b.size"])]); {msg, Msg2Name} -> #cc_msg{type=CMsg2Type} = dict:fetch(Msg2Name, CCMapping), PackFnName = mk_c_fn(p_msg_, Msg2Name), NewMsg2 = case Setter of set -> ?f("~s->mutable_~s()", [MsgVar, CxxFName]); add -> ?f("~s->add_~s()", [MsgVar, CxxFName]); {set_var, V} -> ?f("~s = new ~s()", [V, CMsg2Type]) end, ?f("{\n" " ~s *m2 = ~s;\n" " if (!~s(env, ~s, m2))\n" " return 0;\n" "}\n", [CMsg2Type, NewMsg2, PackFnName, SrcVar]); {map, KeyType, ValueType} -> CMapType = mk_cctype_name(FType, CCMapping), {KeyVar, ValueVar} = SrcVar, PtrDeref = case ValueType of {msg,_} -> "*"; _ -> "" end, KeyDecl = ?f("~s m2k;", [mk_cctype_name(KeyType, CCMapping)]), ValueDecl = ?f("~s ~sm2v;", [mk_cctype_name(ValueType, CCMapping), PtrDeref]), SetKey = format_nif_cc_field_packer_single( KeyVar, MsgVar, Field#?gpb_field{type=KeyType}, Defs, CCMapping, Opts, {set_var, "m2k"}), SetValue = format_nif_cc_field_packer_single( ValueVar, MsgVar, Field#?gpb_field{type=ValueType}, Defs, CCMapping, Opts, {set_var, "m2v"}), ["{\n", ?f(" ~s *map = ~s->mutable_~s();\n" " ~s\n" % decl of m2k " ~s\n" % decl of m2v "\n", [CMapType, MsgVar, CxxFName, KeyDecl, ValueDecl]), %% Set values for m2k and m2v SetKey, SetValue, ?f(" (*map)[m2k] = ~sm2v;\n", [PtrDeref]), "}\n"] end. format_nif_cc_field_packer_repeated(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) -> [?f("{\n" " ERL_NIF_TERM l = ~s;\n" "\n" " while (!enif_is_empty_list(env, l))\n" " {\n" " ERL_NIF_TERM head, tail;\n" "\n" " if (!enif_get_list_cell(env, l, &head, &tail))\n" " return 0;\n", [SrcVar]), "\n", gpb_lib:split_indent_iolist( 4, format_nif_cc_field_packer_single( "head", MsgVar, Field, Defs, CCMapping, Opts, add)), ?f(" l = tail;\n" " }\n" "}\n", [])]. format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) -> case gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) of '2tuples' -> format_nif_cc_field_packer_maptype_r(SrcVar, MsgVar, Field, Defs, CCMapping, Opts); maps -> format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) end. format_nif_cc_field_packer_maptype_r(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) -> ?f("{\n" " ERL_NIF_TERM l = ~s;\n\n" "" " while (!enif_is_empty_list(env, l))\n" " {\n" " ERL_NIF_TERM head, tail;\n\n" "" " if (!enif_get_list_cell(env, l, &head, &tail))\n" " return 0;\n\n" "" " int arity;\n" " const ERL_NIF_TERM *tuple;\n" " if (!enif_get_tuple(env, head, &arity, &tuple))\n" " return 0;\n" " if (arity != 2)\n" " return 0;\n" " ~s\n\n" "" " l = tail;\n" " }\n" "}\n", [SrcVar, gpb_lib:split_indent_butfirst_iolist( 8, format_nif_cc_field_packer_single( {"tuple[0]", "tuple[1]"}, MsgVar, Field, Defs, CCMapping, Opts, add))]). format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field, Defs, CCMapping, Opts) -> ?f("{\n" " ERL_NIF_TERM ik, iv;\n" " ErlNifMapIterator iter;\n" " ErlNifMapIteratorEntry first;\n\n" "" "~s\n\n" %% init of iterator `first' "" " if (!enif_map_iterator_create(env, ~s, &iter, first))\n" " return 0;\n\n" "" " while (enif_map_iterator_get_pair(env, &iter, &ik, &iv))\n" " {\n" " ~s\n" " enif_map_iterator_next(env, &iter);\n" " }\n" " enif_map_iterator_destroy(env, &iter);\n" "}\n", [initialize_map_iterator(4, "first"), SrcVar, gpb_lib:split_indent_butfirst_iolist( 8, format_nif_cc_field_packer_single( {"ik", "iv"}, MsgVar, Field, Defs, CCMapping, Opts, add))]). format_nif_cc_decoders(Mod, Defs, CCMapping, Opts) -> [format_nif_cc_decoder(Mod, MsgName, Fields, CCMapping, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_decoder(_Mod, MsgName, _Fields, CCMapping, _Opts) -> FnName = mk_c_fn(decode_msg_, MsgName), UnpackFnName = mk_c_fn(u_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), ["static ERL_NIF_TERM\n", FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n", "{\n", " ErlNifBinary data;\n", " ERL_NIF_TERM res;\n", " ",CMsgType," *m = new ",CMsgType,"();\n", "\n" " if (argc != 1)\n", " {\n", " delete m;\n", " return enif_make_badarg(env);\n", " }\n", "\n", " if (m == NULL)\n", " {\n", " delete m;\n", " return enif_make_badarg(env);\n", %% FIXME: enomem? " }\n", "\n", " if (!enif_inspect_binary(env, argv[0], &data))\n" " {\n", " delete m;\n", " return enif_make_badarg(env);\n", " }\n", "\n", " if (!m->ParseFromArray(data.data, data.size))\n", " {\n", " delete m;\n", " return enif_make_badarg(env);\n", " }\n", "\n", " res = ",UnpackFnName,"(env, m);\n", " delete m;\n", " return res;\n", "}\n" "\n"]. format_nif_cc_unpackers(_Mod, Defs, CCMapping, Opts) -> [format_nif_cc_unpacker(MsgName, Fields, Defs, CCMapping, Opts) || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)]. format_nif_cc_unpacker(MsgName, Fields, Defs, CCMapping, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, UnpackFnName = mk_c_fn(u_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), IFields = gpb_lib:index_seq(Fields), Is = [I || {I,_} <- IFields], IsProto3 = gpb:is_msg_proto3(MsgName, Defs), %% Initialize the keys to silence "may be used uninitialized" %% warnings for oneof fields for maps (not set in the default, %% but checked for instead by checking for no_value) {KeyVarType, KeyInitExpr} = case gpb_lib:get_maps_key_type_by_opts(Opts) of binary -> {"const char *", "NULL"}; atom -> {"ERL_NIF_TERM ", "gpb_x_no_value"} end, ["static ERL_NIF_TERM\n", UnpackFnName,"(ErlNifEnv *env, const ",CMsgType," *m)\n", "{\n", " ERL_NIF_TERM res;\n", [?f(" ERL_NIF_TERM rname = ~s;\n", [mk_c_var(gpb_aa_, MsgName)]) || not Maps], [ ?f(" ERL_NIF_TERM elem~w;\n", [I]) || I <- Is], [?f(" ~s" "key~w = ~s;\n", [KeyVarType, I, KeyInitExpr]) || I <- Is, Maps], "\n", %% Unpack each field [case Field of #?gpb_field{name=FName} -> DestVar = ?f("elem~w",[I]), gpb_lib:split_indent_iolist( 4, [[?f("key~w = gpb_fa_~s;\n", [I, FName]) || Maps], format_nif_cc_field_unpacker(DestVar, "m", MsgName, Field, Defs, CCMapping, Opts, IsProto3)]); #gpb_oneof{} -> {KSetter, VSetter, UndefSetter} = calc_oneof_key_value_updaters(I, Field, Opts), gpb_lib:split_indent_iolist( 4, format_nif_cc_field_oneof_unpacker( "m", MsgName, Field, KSetter, VSetter, UndefSetter, Defs, CCMapping, Opts, IsProto3)) end || {I, Field} <- IFields], "\n", %% Wrap the result into a record or map case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> ?f(" res = enif_make_tuple(env, ~w, rname~s);\n", [length(Fields) + 1, [?f(", elem~w",[I]) || I <- Is]]); #maps{unset_optional=present_undefined} -> [?f(" res = enif_make_new_map(env);\n"), [?f(" enif_make_map_put(env, res,\n" " mk_key(env, gpb_fa_~s), elem~w,\n" " &res);\n", [gpb_lib:get_field_name(Field), I]) || {I, Field} <- IFields]]; #maps{unset_optional=omitted} -> [?f(" res = enif_make_new_map(env);\n"), [begin Put = ?f("enif_make_map_put(env, res,\n" " mk_key(env, key~w),\n" " elem~w,\n" " &res);", [I, I]), Test = ?f("if (!enif_is_identical(elem~w, gpb_x_no_value))", [I]), PutLine = Put ++ "\n", TestLine = Test ++ "\n", case gpb_lib:get_field_occurrence(Field) of optional -> gpb_lib:indent_lines( 4, [TestLine, gpb_lib:indent(4, PutLine)]); _ -> gpb_lib:indent(4, PutLine) end end || {I, Field} <- IFields]] end, " return res;\n" "}\n", "\n"]. calc_oneof_key_value_updaters(I, #gpb_oneof{name=FName}, Opts) -> case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> {fun(_Tag) -> nop end, fun(Tag,V) -> ?f("elem~w = enif_make_tuple2(env, gpb_aa_~s, ~s)", [I, Tag, V]) end, fun() -> ?f("elem~w = gpb_x_no_value", [I]) end}; #maps{oneof=tuples} -> {fun(_Tag) -> ?f("key~w = gpb_fa_~s", [I, FName]) end, fun(Tag,V) -> ?f("elem~w = enif_make_tuple2(env, gpb_aa_~s, ~s)", [I, Tag, V]) end, fun() -> ?f("elem~w = gpb_x_no_value", [I]) end}; #maps{oneof=flat} -> {fun(Tag) -> ?f("key~w = gpb_fa_~s", [I, Tag]) end, fun(_Tag,V) -> ?f("elem~w = ~s", [I, V]) end, fun() -> ?f("elem~w = gpb_x_no_value", [I]) end} end. format_nif_cc_field_unpacker(DestVar, MsgVar, _MsgName, #?gpb_field{}=Field, Defs, CCMapping, Opts, IsProto3) -> #?gpb_field{occurrence=Occurrence, type=Type}=Field, case Occurrence of required -> format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, CCMapping, IsProto3); optional -> format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, CCMapping, IsProto3); repeated -> case Type of {map,_,_} -> format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field, Defs, CCMapping, Opts); _ -> format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs, CCMapping) end end. format_nif_cc_field_oneof_unpacker(MsgVar, MsgName, #gpb_oneof{name=OFName, fields=OFields}, KSetter, VSetter, UndefSetter, Defs, CCMapping, _Opts, _IsProto3) -> #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), UCOFName = to_upper(OFName), [?f("switch (~s->~s_case())\n", [MsgVar, OFName]), ?f("{\n"), [begin CamelCaseFOFName = camel_case(FOFName), KSetExpr = KSetter(FOFName), VSetExpr = VSetter(FOFName, "ores"), gpb_lib:split_indent_iolist( 4, [?f("case ~s::k~s:\n", [CMsgType, CamelCaseFOFName]), ?f(" {\n"), ?f(" ERL_NIF_TERM ores;\n"), gpb_lib:split_indent_iolist( 8, [format_nif_cc_field_unpacker_by_field("ores", MsgVar, OField, Defs, CCMapping), [[KSetExpr,";\n"] || KSetExpr /= nop], [VSetExpr,";\n"]]), ?f(" }\n"), ?f(" break;\n\n")]) end || #?gpb_field{name=FOFName}=OField <- OFields], gpb_lib:split_indent_iolist( 4, [?f("case ~s::~s_NOT_SET: /* FALL THROUGH */~n", [CMsgType, UCOFName]), ?f("default:~n"), gpb_lib:split_indent_iolist(4, [UndefSetter(),";\n"])]), ?f("}\n"), "\n"]. format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, CCMapping, IsProto3) -> if IsProto3 -> format_nif_cc_field_unpacker_single_p3( DestVar, MsgVar, Field, Defs, CCMapping); not IsProto3 -> format_nif_cc_field_unpacker_single_p2( DestVar, MsgVar, Field, Defs, CCMapping) end. format_nif_cc_field_unpacker_single_p3(DestVar, MsgVar, Field, Defs, CCMapping) -> [format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field, Defs, CCMapping), "\n"]. format_nif_cc_field_unpacker_single_p2(DestVar, MsgVar, Field, Defs, CCMapping) -> #?gpb_field{name=FName} = Field, CxxFName = 'field_name_to_c++'(FName), ?f("if (!~s->has_~s())\n" " ~s = gpb_x_no_value;\n" "else\n" "~s\n" "\n", [MsgVar, CxxFName, DestVar, split_indent_iolist_unless_curly_block( 4, format_nif_cc_field_unpacker_by_field( DestVar, MsgVar, Field, Defs, CCMapping))]). format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field, Defs, CCMapping) -> #?gpb_field{name=FName, type=FType} = Field, CxxFName = 'field_name_to_c++'(FName), SrcExpr = ?f("~s->~s()", [MsgVar, CxxFName]), format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType, Defs, CCMapping). format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType, Defs, CCMapping) -> case FType of float -> [?f("{\n"), ?f(" float v = ~s;\n", [SrcExpr]), ?f(" if (isnan(v))\n"), ?f(" ~s = gpb_aa_nan;\n", [DestVar]), ?f(" else if (isinf(v) && v < 0)\n", []), ?f(" ~s = gpb_aa_minfinity;\n", [DestVar]), ?f(" else if (isinf(v))\n"), ?f(" ~s = gpb_aa_infinity;\n", [DestVar]), ?f(" else\n", []), ?f(" ~s = enif_make_double(env, (double)v);\n", [DestVar]), ?f("}\n")]; double -> [?f("{\n"), ?f(" double v = ~s;\n", [SrcExpr]), ?f(" if (isnan(v))\n"), ?f(" ~s = gpb_aa_nan;\n", [DestVar]), ?f(" else if (isinf(v) && v < 0)\n", []), ?f(" ~s = gpb_aa_minfinity;\n", [DestVar]), ?f(" else if (isinf(v))\n"), ?f(" ~s = gpb_aa_infinity;\n", [DestVar]), ?f(" else\n", []), ?f(" ~s = enif_make_double(env, v);\n", [DestVar]), ?f("}\n")]; _S32 when FType == sint32; FType == int32; FType == sfixed32 -> [?f("~s = enif_make_int(env, ~s);\n", [DestVar, SrcExpr])]; _S64 when FType == sint64; FType == int64; FType == sfixed64 -> [?f("~s = enif_make_int64(env, (ErlNifSInt64)~s);\n", [DestVar, SrcExpr])]; _U32 when FType == uint32; FType == fixed32 -> [?f("~s = enif_make_uint(env, ~s);\n", [DestVar, SrcExpr])]; _U64 when FType == uint64; FType == fixed64 -> [?f("~s = enif_make_uint64(env, (ErlNifUInt64)~s);\n", [DestVar, SrcExpr])]; bool -> [?f("if (~s)\n", [SrcExpr]), ?f(" ~s = gpb_aa_true;\n", [DestVar]), ?f("else\n"), ?f(" ~s = gpb_aa_false;\n", [DestVar])]; {enum, EnumName} -> #cc_enum{unaliased_enums=CCEnums} = dict:fetch(EnumName, CCMapping), [] ++ [?f("switch (~s) {\n", [SrcExpr])] ++ [?f(" case ~s: ~s = ~s; break;\n", [CCSym, DestVar, mk_c_var(gpb_aa_, Sym)]) || {Sym, CCSym} <- CCEnums] ++ [?f(" default: ~s = gpb_aa_undefined;\n", [DestVar])] ++ [?f("}\n")]; string -> [?f("{\n"), ?f(" const char *sData = ~s.data();\n", [SrcExpr]), ?f(" unsigned int sSize = ~s.size();\n", [SrcExpr]), ?f(" ~s = utf8_to_erl_string(env, sData, sSize);\n", [DestVar]), ?f("}\n")]; bytes -> [?f("{\n"), ?f(" unsigned char *data;\n"), ?f(" unsigned int bSize = ~s.size();\n", [SrcExpr]), ?f(" const char *bData = ~s.data();\n", [SrcExpr]), ?f(" data = enif_make_new_binary(\n"), %% can data be NULL?? ?f(" env,\n"), ?f(" bSize,\n"), ?f(" &~s);\n", [DestVar]), ?f(" memmove(data, bData, bSize);\n"), ?f("}\n")]; {msg, Msg2Name} -> UnpackFnName = mk_c_fn(u_msg_, Msg2Name), [?f("~s = ~s(env, &~s);\n", [DestVar, UnpackFnName, SrcExpr])]; {group, Name} -> FType1 = {msg,Name}, format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType1, Defs, CCMapping) end. format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs, CCMapping) -> #?gpb_field{name=FName, type=FType} = Field, CxxFName = 'field_name_to_c++'(FName), [?f("{\n"), ?f(" unsigned int numElems = ~s->~s_size();\n", [MsgVar, CxxFName]), ?f(" ERL_NIF_TERM relem[numElems];\n"), ?f(" unsigned int i;\n"), "\n", ?f(" for (i = 0; i < numElems; i++)\n"), gpb_lib:split_indent_iolist( 4, split_indent_iolist_unless_curly_block( 4, format_nif_cc_field_unpacker_by_type( "relem[i]", ?f("~s->~s(i)", [MsgVar, CxxFName]), FType, Defs, CCMapping))), ?f(" ~s = enif_make_list_from_array(env, relem, numElems);\n", [DestVar]), "}\n", "\n"]. format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field, Defs, CCMapping, Opts) -> #?gpb_field{name=FName, type={map, KeyType, ValueType}=Type} = Field, CxxFName = 'field_name_to_c++'(FName), ItType = mk_cctype_name(Type, CCMapping) ++ "::const_iterator", MapsOrTuples = gpb_lib:get_2tuples_or_maps_for_maptype_fields_by_opts(Opts), ["{\n", gpb_lib:split_indent_iolist( 4, case MapsOrTuples of '2tuples' -> [?f("~s = enif_make_list(env, 0);\n", [DestVar]), ?f("int i = 0;\n", [])]; maps -> ?f("~s = enif_make_new_map(env);\n", [DestVar]) end), %% Iterate ?f(" for (~s it = ~s->~s().begin();\n" " it != ~s->~s().end();\n" " ++it)\n", [ItType, MsgVar, CxxFName, MsgVar, CxxFName]), " {\n", " ERL_NIF_TERM ek, ev;\n", %% FIXME gpb_lib:split_indent_iolist( 8, [format_nif_cc_field_unpacker_by_type("ek", "it->first", KeyType, Defs, CCMapping), format_nif_cc_field_unpacker_by_type("ev", "it->second", ValueType, Defs, CCMapping), case MapsOrTuples of '2tuples' -> ["ERL_NIF_TERM eitem = enif_make_tuple2(env, ek, ev);\n", ?f("~s = enif_make_list_cell(env, eitem, ~s);\n", [DestVar, DestVar]), "++i;\n"]; maps -> [?f("enif_make_map_put(env, ~s, ek, ev, &~s);\n", [DestVar, DestVar])] end]), " }\n", "}\n"]. format_nif_cc_to_jsoners(Mod, Defs, CCMapping, Opts) -> [format_nif_cc_to_jsoner(Mod, MsgName, Fields, CCMapping, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_to_jsoner(_Mod, MsgName, _Fields, CCMapping, Opts) -> FnName = mk_c_fn(to_json_msg_, MsgName), PackFnName = mk_c_fn(p_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), AlwaysPrintPrimitives = atom_to_list( proplists:get_bool(json_always_print_primitive_fields, Opts)), PreserveFNames = atom_to_list(proplists:get_bool(json_preserve_proto_field_names, Opts)), %% The preserve_proto_field_names and always_print_enums_as_ints %% JsonPrintOptions fields were added in in 3.3.0, so check for that. %% There is an error check earlier for when it needs to be set to true. %% Here, just don't fail if in the interval 3.0.0 .. 3.2.x. ["static ERL_NIF_TERM\n", FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n", "{\n", " ERL_NIF_TERM result;\n", " ",CMsgType," *m = new ",CMsgType,"();\n", " ::std::string j;\n" " ::google::protobuf::util::JsonPrintOptions jopts;\n\n" "" " jopts.add_whitespace = false;\n" " jopts.always_print_primitive_fields = ",AlwaysPrintPrimitives,";\n" "#if GOOGLE_PROTOBUF_VERSION >= 3003000\n" " jopts.preserve_proto_field_names = ",PreserveFNames,";\n" " jopts.always_print_enums_as_ints = false;\n\n" "#endif\n" "" " if (argc != 1)\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (m == NULL)\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (!",PackFnName,"(env, argv[0], m))\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " if (::google::protobuf::util::MessageToJsonString(*m, &j, jopts)\n" " == ::google::protobuf::util::Status::OK)\n" " {\n" " unsigned char *data;\n" " const char *jData = j.data();\n" " unsigned int jSize = j.size();\n" " data = enif_make_new_binary(env, jSize, &result);\n" " memmove(data, jData, jSize);\n" " }\n" " else\n" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n\n" "" " delete m;\n" " return result;\n" "}\n" "\n"]. format_nif_cc_from_jsoners(Mod, Defs, CCMapping, Opts) -> [format_nif_cc_from_jsoner(Mod, MsgName, Fields, CCMapping, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_from_jsoner(_Mod, MsgName, _Fields, CCMapping, Opts) -> FnName = mk_c_fn(from_json_msg_, MsgName), UnpackFnName = mk_c_fn(u_msg_, MsgName), #cc_msg{type=CMsgType} = dict:fetch(MsgName, CCMapping), CaseInsensitiveEnums = atom_to_list( proplists:get_bool(json_case_insensitive_enum_parsing, Opts)), ["static ERL_NIF_TERM\n", FnName,"(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])\n", "{\n", " ErlNifBinary data;\n", " ERL_NIF_TERM res;\n", " ",CMsgType," *m = new ",CMsgType,"();\n", " ::std::string j;\n" " ::google::protobuf::util::JsonParseOptions jopts;\n" "\n" %% The JsonParseOptions contained ignore_unknown_fields already in 3.0.0 " jopts.ignore_unknown_fields = true;\n" %% Case insensitive enum parsing is default in 3.8.0, %% and the option appeared in 3.7.0 "#if GOOGLE_PROTOBUF_VERSION >= 3007000\n" " jopts.case_insensitive_enum_parsing = ",CaseInsensitiveEnums,";\n\n" "#endif\n" "\n" " if (argc != 1)\n", " {\n", " delete m;\n", " return enif_make_badarg(env);\n", " }\n", "\n", " if (m == NULL)\n", " {\n", " delete m;\n", " return enif_make_badarg(env);\n", %% FIXME: enomem? " }\n", "\n", " if (!enif_inspect_binary(env, argv[0], &data))\n" " {\n", " delete m;\n", " return enif_make_badarg(env);\n", " }\n", "\n", %% try/catch bad_alloc and return enomem? " j.assign(reinterpret_cast(data.data), data.size);\n" " if (::google::protobuf::util::JsonStringToMessage(j, m, jopts)\n" " != ::google::protobuf::util::Status::OK)" " {\n" " delete m;\n" " return enif_make_badarg(env);\n" " }\n" "\n" " res = ",UnpackFnName,"(env, m);\n", " delete m;\n", " return res;\n", "}\n" "\n"]. mk_cctype_name({enum,EnumName}, CCMapping) -> #cc_enum{type = CCType} = dict:fetch(EnumName, CCMapping), CCType; mk_cctype_name({msg,MsgName}, CCMapping) -> #cc_msg{type = CCType} = dict:fetch(MsgName, CCMapping), CCType; mk_cctype_name({group,GName}, CCMapping) -> #cc_msg{type = CCType} = dict:fetch(GName, CCMapping), CCType; mk_cctype_name({map,KeyType,ValueType}, CCMapping) -> CKeyType = mk_cctype_name(KeyType, CCMapping), CValueType = mk_cctype_name(ValueType, CCMapping), "::google::protobuf::Map< " ++ CKeyType ++ ", " ++ CValueType ++ " >"; mk_cctype_name(Type, _CCMapping) -> case Type of sint32 -> "::google::protobuf::int32"; sint64 -> "::google::protobuf::int64"; int32 -> "::google::protobuf::int32"; int64 -> "::google::protobuf::int64"; uint32 -> "::google::protobuf::uint32"; uint64 -> "::google::protobuf::uint64"; bool -> "bool"; fixed64 -> "::google::protobuf::uint64"; sfixed64 -> "::google::protobuf::int64"; double -> "double"; string -> "::std::string"; bytes -> "::std::string"; fixed32 -> "::google::protobuf::uint32"; sfixed32 -> "::google::protobuf::int32"; float -> "float" end. initialize_map_iterator(Indent, IteratorVarName) -> ?f("#if ~s\n" "~s = ERL_NIF_MAP_ITERATOR_FIRST;\n" "#else /* before 2.8 which appeared in 18.0 */\n" "~s = ERL_NIF_MAP_ITERATOR_HEAD;\n" "#endif\n", [format_nif_check_version_or_later(2, 8), gpb_lib:indent(Indent, IteratorVarName), gpb_lib:indent(Indent, IteratorVarName)]). split_indent_iolist_unless_curly_block(Indent, IoList) -> gpb_lib:cond_split_indent_iolist( fun is_not_curly_block/1, Indent, IoList). is_not_curly_block(<<"{", _/binary>>) -> false; is_not_curly_block(_) -> true. to_lower(A) when is_atom(A) -> list_to_atom(gpb_lib:lowercase(atom_to_list(A))). to_upper(A) when is_atom(A) -> list_to_atom(gpb_lib:uppercase(atom_to_list(A))). camel_case(A) when is_atom(A) -> list_to_atom(gpb_lib:camel_case(atom_to_list(A))). mk_c_fn(Prefix, Suffix) -> dot_to_underscore(lists:concat([Prefix, Suffix])). mk_c_var(Prefix, Suffix) -> dot_to_underscore(lists:concat([Prefix, Suffix])). dot_to_underscore(X) when is_list(X) -> dot_replace_s(X, "_"). dot_replace_s(S, New) when is_list(S) -> d_r(S, New); dot_replace_s(S, New) when is_atom(S) -> d_r(atom_to_list(S), New). d_r("."++Rest, New) -> New ++ d_r(Rest, New); d_r([C|Rest], New) -> [C | d_r(Rest, New)]; d_r("", _New) -> "". is_dotted(S) when is_list(S) -> gpb_lib:is_substr(".", S). 'field_name_to_c++'(FName) -> 'sym_to_c++'(to_lower(FName)). 'sym_to_c++'(Sym) -> case 'is_c++_keyword'(Sym) of false -> Sym; true -> underscore_suffix(Sym) end. underscore_suffix(A) when is_atom(A) -> list_to_atom(atom_to_list(A) ++ "_"). 'is_c++_keyword'(alignas) -> true; 'is_c++_keyword'(alignof) -> true; 'is_c++_keyword'('and') -> true; 'is_c++_keyword'(and_eq) -> true; 'is_c++_keyword'(asm) -> true; 'is_c++_keyword'(auto) -> true; 'is_c++_keyword'(bitand) -> true; 'is_c++_keyword'(bitor) -> true; 'is_c++_keyword'(bool) -> true; 'is_c++_keyword'(break) -> true; 'is_c++_keyword'('case') -> true; 'is_c++_keyword'('catch') -> true; 'is_c++_keyword'(char) -> true; 'is_c++_keyword'(class) -> true; 'is_c++_keyword'(compl) -> true; 'is_c++_keyword'(const) -> true; 'is_c++_keyword'(constexpr) -> true; 'is_c++_keyword'(const_cast) -> true; 'is_c++_keyword'(continue) -> true; 'is_c++_keyword'(decltype) -> true; 'is_c++_keyword'(default) -> true; 'is_c++_keyword'(delete) -> true; 'is_c++_keyword'(do) -> true; 'is_c++_keyword'(double) -> true; 'is_c++_keyword'(dynamic_cast) -> true; 'is_c++_keyword'(else) -> true; 'is_c++_keyword'(enum) -> true; 'is_c++_keyword'(explicit) -> true; 'is_c++_keyword'(export) -> true; 'is_c++_keyword'(extern) -> true; 'is_c++_keyword'(false) -> true; 'is_c++_keyword'(float) -> true; 'is_c++_keyword'(for) -> true; 'is_c++_keyword'(friend) -> true; 'is_c++_keyword'(goto) -> true; 'is_c++_keyword'('if') -> true; 'is_c++_keyword'(inline) -> true; 'is_c++_keyword'(int) -> true; 'is_c++_keyword'(long) -> true; 'is_c++_keyword'(mutable) -> true; 'is_c++_keyword'(namespace) -> true; 'is_c++_keyword'(new) -> true; 'is_c++_keyword'(noexcept) -> true; 'is_c++_keyword'('not') -> true; 'is_c++_keyword'(not_eq) -> true; 'is_c++_keyword'(nullptr) -> true; 'is_c++_keyword'(operator) -> true; 'is_c++_keyword'('or') -> true; 'is_c++_keyword'(or_eq) -> true; 'is_c++_keyword'(private) -> true; 'is_c++_keyword'(protected) -> true; 'is_c++_keyword'(public) -> true; 'is_c++_keyword'(register) -> true; 'is_c++_keyword'(reinterpret_cast) -> true; 'is_c++_keyword'(return) -> true; 'is_c++_keyword'(short) -> true; 'is_c++_keyword'(signed) -> true; 'is_c++_keyword'(sizeof) -> true; 'is_c++_keyword'(static) -> true; 'is_c++_keyword'(static_assert) -> true; 'is_c++_keyword'(static_cast) -> true; 'is_c++_keyword'(struct) -> true; 'is_c++_keyword'(switch) -> true; 'is_c++_keyword'(template) -> true; 'is_c++_keyword'(this) -> true; 'is_c++_keyword'(thread_local) -> true; 'is_c++_keyword'(throw) -> true; 'is_c++_keyword'(true) -> true; 'is_c++_keyword'('try') -> true; 'is_c++_keyword'(typedef) -> true; 'is_c++_keyword'(typeid) -> true; 'is_c++_keyword'(typename) -> true; 'is_c++_keyword'(union) -> true; 'is_c++_keyword'(unsigned) -> true; 'is_c++_keyword'(using) -> true; 'is_c++_keyword'(virtual) -> true; 'is_c++_keyword'(void) -> true; 'is_c++_keyword'(volatile) -> true; 'is_c++_keyword'(wchar_t) -> true; 'is_c++_keyword'(while) -> true; 'is_c++_keyword'('xor') -> true; 'is_c++_keyword'(xor_eq) -> true; 'is_c++_keyword'(_) -> false.