Packages
gpb
4.2.2
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_gen_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.