%%% 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_cc/4]). -include("../include/gpb.hrl"). -include("gpb_codegen.hrl"). -include("gpb_compile.hrl"). -import(gpb_lib, [replace_term/2]). 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) -> <<>>. 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(e_msg_, MsgName), fun(Msg) -> erlang:nif_error({error,{nif_not_loaded,''}}, [Msg]) end, [replace_term('', MsgName)]). 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(d_msg_, MsgName), fun(Bin) -> erlang:nif_error({error,{nif_not_loaded,''}}, [Bin]) end, [replace_term('', MsgName)]). format_nif_cc(Mod, 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_local_function_decls(Mod, Defs, Opts), format_nif_cc_mk_consts(Mod, Defs, AnRes, Opts), format_nif_cc_mk_atoms(Mod, Defs, AnRes, Opts), format_nif_cc_utf8_conversion(Mod, Defs, AnRes, Opts), format_nif_cc_encoders(Mod, Defs, Opts), format_nif_cc_packers(Mod, Defs, Opts), format_nif_cc_decoders(Mod, Defs, Opts), format_nif_cc_unpackers(Mod, Defs, Opts), format_nif_cc_foot(Mod, Defs, Opts)]). get_cc_pkg(Defs) -> case lists:keyfind(package, 1, Defs) of false -> ""; {package, Package} -> "::"++dot_replace_s(Package, "::") end. 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", ["#include \n" || is_any_field_of_type_float_or_double(AnRes)], "\n", "#include \n", "\n", ?f("#include \"~s.pb.h\"\n", [Mod]), ["#include \n" || IsLiteRT], "\n"]. 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_local_function_decls(_Mod, Defs, _Opts) -> CPkg = get_cc_pkg(Defs), [[begin PackFnName = mk_c_fn(p_msg_, MsgName), UnpackFnName = mk_c_fn(u_msg_, MsgName), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), [["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, EnumAtoms = lists:flatten([[Sym || {Sym, _V} <- EnumDef] || {{enum, _}, EnumDef} <- Defs]), RecordAtoms = [MsgName || {_, MsgName, _Fields} <- gpb_lib:msgs_or_groups(Defs)], OneofNames0 = collect_oneof_fields(Defs), MiscAtoms0 = case is_any_field_of_type_enum(AnRes) of true -> [undefined]; false -> [] end, MiscAtoms1 = case is_any_field_of_type_bool(AnRes) of true -> MiscAtoms0 ++ [true, false]; false -> MiscAtoms0 end, MiscAtoms2 = case is_any_field_of_type_float_or_double(AnRes) of true -> MiscAtoms1 ++ [infinity, '-infinity', nan]; false -> MiscAtoms1 end, FieldAtoms0 = if Maps -> lists:flatten( [[gpb_lib:get_field_name(Field) || Field <- Fields] || {_, _Name, Fields} <- gpb_lib:msgs_or_groups( Defs)]); not Maps -> [] end, {FieldAtoms, OneofNames} = case gpb_lib:get_mapping_and_unset_by_opts(Opts) of #maps{oneof=flat} -> {lists:usort(FieldAtoms0 ++ OneofNames0), []}; _ -> {lists:usort(FieldAtoms0), lists:usort(OneofNames0)} end, Atoms = lists:usort(EnumAtoms ++ RecordAtoms ++ OneofNames ++ MiscAtoms2), AtomVars0 = [{mk_c_var(gpb_aa_, minus_to_m(A)), A} || A <- Atoms], NoValue = case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> undefined; #maps{unset_optional=present_undefined} -> undefined; #maps{unset_optional=omitted} -> '$undef' end, AtomVars1 = [{"gpb_x_no_value", NoValue} | AtomVars0], FieldAtomVars = [{mk_c_var(gpb_fa_, minus_to_m(A)), A} || A <- FieldAtoms], [[?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- AtomVars1], [?f("static ERL_NIF_TERM ~s;\n", [Var]) || {Var,_Atom} <- FieldAtomVars], "\n", ["static void install_atoms(ErlNifEnv *env)\n" "{\n", [?f(" ~s = enif_make_atom(env, \"~s\");\n", [AtomVar, Atom]) || {AtomVar, Atom} <- AtomVars1], [?f(" ~s = enif_make_atom(env, \"~s\");\n", [AtomVar, Atom]) || {AtomVar, Atom} <- FieldAtomVars], "}\n", "\n"]]. 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. minus_to_m(A) -> case atom_to_list(A) of "-"++Rest -> "m"++Rest; _ -> A end. collect_oneof_fields(Defs) -> lists:usort( lists:flatten( [[[FOFName || #?gpb_field{name=FOFName} <- OFields] || #gpb_oneof{fields=OFields} <- Fields] || {_msg_or_group, _, Fields} <- gpb_lib:msgs_or_groups(Defs)])). 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, "), "#else /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n", format_nif_cc_nif_funcs_list(Defs, ""), "#endif /* ERL_NIF_DIRTY_SCHEDULER_SUPPORT */\n", "#else /* before 2.7 or 17.3 */\n", format_nif_cc_nif_funcs_list(Defs, no_flags), "#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) -> MsgNames = [MsgName || {{msg, MsgName}, _MsgFields} <- Defs], FlagStr = if Flags == no_flags -> ""; true -> ", " ++ Flags end, [begin EncodeFnName = gpb_lib:mk_fn(e_msg_, MsgName), EncodeCFnName = mk_c_fn(e_msg_, MsgName), DecodeFnName = gpb_lib:mk_fn(d_msg_, MsgName), DecodeCFnName = mk_c_fn(d_msg_, MsgName), IsLast = I == length(MsgNames), Comma = ["," || not IsLast], [?f(" {\"~s\", 1, ~s~s},\n", [EncodeFnName, EncodeCFnName, FlagStr]), ?f(" {\"~s\", 1, ~s~s}~s\n", [DecodeFnName, DecodeCFnName, FlagStr, Comma])] end || {I, MsgName} <- gpb_lib:index_seq(MsgNames)]. format_nif_cc_encoders(Mod, Defs, Opts) -> CPkg = get_cc_pkg(Defs), [format_nif_cc_encoder(Mod, CPkg, MsgName, Fields, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_encoder(_Mod, CPkg, MsgName, _Fields, _Opts) -> FnName = mk_c_fn(e_msg_, MsgName), PackFnName = mk_c_fn(p_msg_, MsgName), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), ["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, Opts) -> CPkg = get_cc_pkg(Defs), [format_nif_cc_packer(CPkg, MsgName, Fields, Defs, Opts) || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)]. format_nif_cc_packer(CPkg, MsgName, MsgFields, Defs, 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), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), ["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 (enif_is_identical(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, 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, 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, Opts) -> #?gpb_field{occurrence=Occurrence, type=Type}=Field, case Occurrence of required -> format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, Opts, set); optional -> format_nif_cc_field_packer_optional(SrcVar, MsgVar, Field, Defs, Opts); repeated -> case Type of {map,_,_} -> format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field, Defs, Opts); _ -> format_nif_cc_field_packer_repeated(SrcVar, MsgVar, Field, Defs, Opts) end end; format_nif_cc_field_packer(SrcVar, MsgVar, #gpb_oneof{}=Field, Defs, 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, Opts))]), "\n"]. format_nif_cc_oneof_packer(NameVar, SrcVar, MsgVar, OFields, Defs, 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, 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, Opts) -> [?f("if (!enif_is_identical(~s, gpb_x_no_value))\n", [SrcVar]), format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, Opts, set)]. format_nif_cc_field_packer_single(SrcVar, MsgVar, #?gpb_field{type={group,Name}}=Field, Defs, Opts, Setter) -> format_nif_cc_field_packer_single( SrcVar, MsgVar, Field#?gpb_field{type={msg,Name}}, Defs, Opts, Setter); format_nif_cc_field_packer_single(SrcVar, MsgVar, Field, Defs, Opts, Setter) -> #?gpb_field{name=FName, type=FType} = Field, LCFName = to_lower(FName), SetFn = fun(Exprs) -> case Setter of set -> ?f("~s->set_~s(~s);", [MsgVar, LCFName, gpb_lib:comma_join(Exprs)]); add -> ?f("~s->add_~s(~s);", [MsgVar, LCFName, 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} -> EPrefix = case is_dotted(EnumName) of false -> ""; true -> dot_replace_s(EnumName, "_") ++ "_" end, CPkg = get_cc_pkg(Defs), {value, {{enum,EnumName}, Enumerations}} = lists:keysearch({enum,EnumName}, 1, Defs), ["{\n", [?f(" ~sif (enif_is_identical(~s, ~s))\n" " ~s\n", [if I == 1 -> ""; I > 1 -> "else " end, SrcVar, mk_c_var(gpb_aa_, Sym), SetFn([?f("~s::~s~s", [CPkg, EPrefix, Sym])])]) || {I, {Sym, _Val}} <- gpb_lib:index_seq(Enumerations)], " 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} -> CMsg2Type = mk_cctype_name(FType, Defs), PackFnName = mk_c_fn(p_msg_, Msg2Name), NewMsg2 = case Setter of set -> ?f("~s->mutable_~s()", [MsgVar, LCFName]); add -> ?f("~s->add_~s()", [MsgVar, LCFName]); {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, Defs), {KeyVar, ValueVar} = SrcVar, PtrDeref = case ValueType of {msg,_} -> "*"; _ -> "" end, KeyDecl = ?f("~s m2k;", [mk_cctype_name(KeyType, Defs)]), ValueDecl = ?f("~s ~sm2v;", [mk_cctype_name(ValueType, Defs), PtrDeref]), SetKey = format_nif_cc_field_packer_single( KeyVar, MsgVar, Field#?gpb_field{type=KeyType}, Defs, Opts, {set_var, "m2k"}), SetValue = format_nif_cc_field_packer_single( ValueVar, MsgVar, Field#?gpb_field{type=ValueType}, Defs, 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, LCFName, 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, 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, Opts, add)), ?f(" l = tail;\n" " }\n" "}\n", [])]. format_nif_cc_field_packer_maptype(SrcVar, MsgVar, Field, Defs, 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, Opts); maps -> format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field, Defs, Opts) end. format_nif_cc_field_packer_maptype_r(SrcVar, MsgVar, Field, Defs, 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, Opts, add))]). format_nif_cc_field_packer_maptype_m(SrcVar, MsgVar, Field, Defs, 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, Opts, add))]). format_nif_cc_decoders(Mod, Defs, Opts) -> CPkg = get_cc_pkg(Defs), [format_nif_cc_decoder(Mod, CPkg, MsgName, Fields, Opts) || {{msg, MsgName}, Fields} <- Defs]. format_nif_cc_decoder(_Mod, CPkg, MsgName, _Fields, _Opts) -> FnName = mk_c_fn(d_msg_, MsgName), UnpackFnName = mk_c_fn(u_msg_, MsgName), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), ["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, Opts) -> CPkg = get_cc_pkg(Defs), [format_nif_cc_unpacker(CPkg, MsgName, Fields, Defs, Opts) || {_msg_or_group, MsgName, Fields} <- gpb_lib:msgs_or_groups(Defs)]. format_nif_cc_unpacker(CPkg, MsgName, Fields0, Defs, Opts) -> Maps = gpb_lib:get_records_or_maps_by_opts(Opts) == maps, Fields1 = case gpb_lib:get_mapping_and_unset_by_opts(Opts) of #maps{unset_optional=omitted, oneof=flat} -> gpb_lib:flatten_oneof_fields(Fields0); _ -> Fields0 end, UnpackFnName = mk_c_fn(u_msg_, MsgName), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), Is = [I || {I,_} <- gpb_lib:index_seq(Fields1)], IsProto3 = gpb:is_msg_proto3(MsgName, Defs), ["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], "\n", [begin DestVar = ?f("elem~w",[I]), gpb_lib:split_indent_iolist( 4, format_nif_cc_field_unpacker(DestVar, "m", MsgName, Field, Defs, Opts, IsProto3)) end || {I, Field} <- gpb_lib:index_seq(Fields1)], "\n", case gpb_lib:get_mapping_and_unset_by_opts(Opts) of records -> ?f(" res = enif_make_tuple(env, ~w, rname~s);\n", [length(Fields1) + 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, gpb_fa_~s, elem~w, &res);\n", [gpb_lib:get_field_name(Field), I]) || {I, Field} <- gpb_lib:index_seq(Fields1)]]; #maps{unset_optional=omitted} -> [?f(" res = enif_make_new_map(env);\n"), [begin Put = ?f("enif_make_map_put("++ "env, res, gpb_fa_~s, elem~w, &res);", [gpb_lib:get_field_name(Field), 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} <- gpb_lib:index_seq(Fields1)]] end, " return res;\n" "}\n", "\n"]. format_nif_cc_field_unpacker(DestVar, MsgVar, _MsgName, #?gpb_field{}=Field, Defs, Opts, IsProto3) -> #?gpb_field{occurrence=Occurrence, type=Type}=Field, case Occurrence of required -> format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, IsProto3); optional -> format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, IsProto3); repeated -> case Type of {map,_,_} -> format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field, Defs, Opts); _ -> format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs) end end; format_nif_cc_field_unpacker(DestVar, MsgVar, MsgName, #gpb_oneof{}=Field, Defs, _Opts, _IsProto3) -> #gpb_oneof{name=OFName, fields=OFields} = Field, CPkg = get_cc_pkg(Defs), CMsgType = CPkg ++ "::" ++ dot_replace_s(MsgName, "::"), LCOFName = to_lower(OFName), UCOFName = to_upper(OFName), [?f("switch (~s->~s_case())\n", [MsgVar, LCOFName]), ?f("{\n"), [begin CamelCaseFOFName = camel_case(FOFName), AtomVar = mk_c_var(gpb_aa_, FOFName), 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)), ?f(" ~s = enif_make_tuple2(env, ~s, ores);\n", [DestVar, AtomVar]), ?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"), ?f(" ~s = gpb_x_no_value;\n", [DestVar])]), ?f("}\n"), "\n"]. format_nif_cc_field_unpacker_single(DestVar, MsgVar, Field, Defs, IsProto3) -> if IsProto3 -> format_nif_cc_field_unpacker_single_p3( DestVar, MsgVar, Field, Defs); not IsProto3 -> format_nif_cc_field_unpacker_single_p2( DestVar, MsgVar, Field, Defs) end. format_nif_cc_field_unpacker_single_p3(DestVar, MsgVar, Field, Defs) -> [format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field, Defs), "\n"]. format_nif_cc_field_unpacker_single_p2(DestVar, MsgVar, Field, Defs) -> #?gpb_field{name=FName} = Field, LCFName = to_lower(FName), ?f("if (!~s->has_~s())\n" " ~s = gpb_x_no_value;\n" "else\n" "~s\n" "\n", [MsgVar, LCFName, DestVar, split_indent_iolist_unless_curly_block( 4, format_nif_cc_field_unpacker_by_field( DestVar, MsgVar, Field, Defs))]). format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field, Defs) -> #?gpb_field{name=FName, type=FType} = Field, LCFName = to_lower(FName), SrcExpr = ?f("~s->~s()", [MsgVar, LCFName]), format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType, Defs). format_nif_cc_field_unpacker_by_type(DestVar, SrcExpr, FType, Defs) -> 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} -> EPrefix = case is_dotted(EnumName) of false -> ""; true -> dot_replace_s(EnumName, "_") ++ "_" end, CPkg = get_cc_pkg(Defs), {value, {{enum,EnumName}, Enumerations}} = lists:keysearch({enum,EnumName}, 1, Defs), [] ++ [?f("switch (~s) {\n", [SrcExpr])] ++ [?f(" case ~s::~s~s: ~s = ~s; break;\n", [CPkg, EPrefix, Sym, DestVar, mk_c_var(gpb_aa_, Sym)]) || {Sym, _Value} <- Enumerations] ++ [?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) end. format_nif_cc_field_unpacker_repeated(DestVar, MsgVar, Field, Defs) -> #?gpb_field{name=FName, type=FType} = Field, LCFName = to_lower(FName), [?f("{\n"), ?f(" unsigned int numElems = ~s->~s_size();\n", [MsgVar, LCFName]), ?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, LCFName]), FType, Defs))), ?f(" ~s = enif_make_list_from_array(env, relem, numElems);\n", [DestVar]), "}\n", "\n"]. format_nif_cc_field_unpacker_maptype(DestVar, MsgVar, Field, Defs, Opts) -> #?gpb_field{name=FName, type={map, KeyType, ValueType}=Type} = Field, LCFName = to_lower(FName), ItType = mk_cctype_name(Type, Defs) ++ "::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, LCFName, MsgVar, LCFName]), " {\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), format_nif_cc_field_unpacker_by_type("ev", "it->second", ValueType, Defs), 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"]. mk_cctype_name({enum,EnumName}, Defs) -> EPrefix = case is_dotted(EnumName) of false -> atom_to_list(EnumName); true -> dot_replace_s(EnumName, "_") end, CPkg = get_cc_pkg(Defs), CPkg ++ "::" ++ EPrefix; mk_cctype_name({msg,MsgName}, Defs) -> CPkg = get_cc_pkg(Defs), CPkg ++ "::" ++ dot_replace_s(MsgName, "::"); mk_cctype_name({group,Name}, Defs) -> mk_cctype_name({msg,Name}, Defs); mk_cctype_name({map,KeyType,ValueType}, Defs) -> CKeyType = mk_cctype_name(KeyType, Defs), CValueType = mk_cctype_name(ValueType, Defs), "::google::protobuf::Map< " ++ CKeyType ++ ", " ++ CValueType ++ " >"; mk_cctype_name(Type, _Defs) -> 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(camel_case(atom_to_list(A), true)). -define(is_lower_case(C), $a =< C, C =< $z). -define(is_upper_case(C), $A =< C, C =< $Z). -define(is_digit(C), $0 =< C, C =< $9). camel_case([LC | Tl], CapNextLetter) when ?is_lower_case(LC) -> if CapNextLetter -> [capitalize_letter(LC) | camel_case(Tl, false)]; not CapNextLetter -> [LC | camel_case(Tl, false)] end; camel_case([UC | Tl], _) when ?is_upper_case(UC) -> [UC | camel_case(Tl, false)]; camel_case([D | Tl], _) when ?is_digit(D) -> [D | camel_case(Tl, true)]; camel_case([_ | Tl], _) -> %% underscore and possibly more camel_case(Tl, true); camel_case([], _) -> []. capitalize_letter(C) -> C + ($A - $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); is_dotted(S) when is_atom(S) -> is_dotted(atom_to_list(S)).