Current section

Files

Jump to
gpb src gpb_gen_nif.erl
Raw

src/gpb_gen_nif.erl

%%% Copyright (C) 2017 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA 02110-1301 USA
%%% @doc Generation of 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) ->
<<ModBin/binary, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
replace_tilde_s(<<"{{loadinfo}}", Rest/binary>>, ModBin, VsnBin) ->
<<VsnBin/binary, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
replace_tilde_s(<<C, Rest/binary>>, ModBin, VsnBin) ->
<<C, (replace_tilde_s(Rest, ModBin, VsnBin))/binary>>;
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-name>'}}, [Msg])
end,
[replace_term('<msg-name>', 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,'<msg-name>'}}, [Bin])
end,
[replace_term('<msg-name>', 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 <string.h>\n",
"#include <string>\n",
["#include <math.h>\n" || is_any_field_of_type_float_or_double(AnRes)],
"\n",
"#include <erl_nif.h>\n",
"\n",
?f("#include \"~s.pb.h\"\n", [Mod]),
["#include <google/protobuf/message_lite.h>\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",
" \"<invalid UTF-8>\",\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,
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} ->
EPrefix = case is_dotted(EnumName) of
false -> "";
true -> dot_replace_s(EnumName, "_") ++ "_"
end,
CPkg = get_cc_pkg(Defs),
EType = mk_cctype_name({enum, EnumName}, Defs),
{value, {{enum,EnumName}, Enumerations}} =
lists:keysearch({enum,EnumName}, 1, Defs),
["{\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([?f("~s::~s~s", [CPkg, EPrefix, 'sym_to_c++'(Sym)])])])
|| {Sym, _Val} <- 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<char *>(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<char *>(b.data)", "b.size"]),
SrcVar, SrcVar, SetFn(["reinterpret_cast<char *>(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, 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, 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, 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, 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, "::"),
UCOFName = to_upper(OFName),
[?f("switch (~s->~s_case())\n", [MsgVar, OFName]),
?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,
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))]).
format_nif_cc_field_unpacker_by_field(DestVar, MsgVar, Field, Defs) ->
#?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).
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_to_c++'(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,
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))),
?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,
CxxFName = 'field_name_to_c++'(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, 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),
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)).
'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.