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