Packages
gpb
4.19.6
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_decoders_lib.erl
%%% Copyright (C) 2019 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
%%% @private
%%% @doc Common functions for gpb_gen_decoders and gpb_gen_json_decoders
-module(gpb_decoders_lib).
-export([init_exprs/6]).
-export([calc_field_infos/2]).
-export([decoder_read_field_param/3]).
-export([decoder_maybe_check_required_present/3]).
-export([decoder_finalize_result/5]).
-export([run_morph_ops/2]).
-export([underscore_unused_vars/0]).
-export([explode_param_init/3]).
-export([explode_param_pass/3]).
-export([change_undef_marker_in_clauses/1]).
-export([implode_to_map_exprs_all_mandatory/0]).
-export([implode_to_map_exprs/3]).
-export([rework_records_to_maps/3]).
-export([finalize_marked_map_exprs/1]).
-include("../include/gpb.hrl").
-include("gpb_codegen.hrl").
-include("gpb_compile.hrl").
-include("gpb_decoders_lib.hrl").
-import(gpb_lib, [replace_term/2, replace_tree/2]).
%% @doc Generate field initialization expressions.
%% Such expressions include `[]' initially for repeated fields, or the
%% proto3 type-default value for optional fields (or `undefined' for
%% optional fields in proto2 messages). Also of concern is that there
%% may be translators that set other initial values.
init_exprs(MsgName, MsgDef, Defs, TrUserDataVar, AnRes, Opts)->
UseDefaults = proplists:get_bool(defaults_for_omitted_optionals, Opts),
UseTypeDefaults = proplists:get_bool(type_defaults_for_omitted_optionals,
Opts),
DecVfy = proplists:get_bool(verify_decode_required_present, Opts),
MappingUnset = gpb_lib:get_mapping_and_unset_by_opts(Opts),
R = % Whether required fields are present in init-exprs
case MappingUnset of
records -> o;
#maps{} -> if DecVfy -> m;
true -> o
end
end,
ExprInfos1 =
[case Field of
#?gpb_field{name=FName, occurrence=Occurrence, type=Type,
opts=FOpts} ->
HasDefault = lists:keymember(default, 1, FOpts),
SubMsgType = is_msg_type(Type),
{Undefined, Undef, P} =
if SubMsgType ->
{?expr(undefined), ?expr('$undef'), o};
Occurrence == defaulty ->
TD = gpb_lib:proto3_type_default(Type, Defs, Opts),
ATD = erl_syntax:abstract(TD),
{ATD, ATD, o};
UseDefaults, HasDefault ->
{default,D} = lists:keyfind(default, 1, FOpts),
AD = erl_syntax:abstract(D),
{AD, AD, m};
UseTypeDefaults ->
TD = gpb_lib:proto2_type_default(Type, Defs, Opts),
ATD = erl_syntax:abstract(TD),
{ATD, ATD, m};
true ->
Pr = if HasDefault -> d;
true -> o
end,
{?expr(undefined), ?expr('$undef'), Pr}
end,
case Occurrence of
repeated -> {FName, m, ?expr([]), ?expr([])};
required -> {FName, R, ?expr(undefined), ?expr('$undef')};
optional -> {FName, P, Undefined, Undef};
defaulty -> {FName, o, Undefined, Undef}
end;
#gpb_oneof{name=FName} ->
{FName, o, ?expr(undefined), ?expr('$undef')}
end
|| Field <- MsgDef],
ExprInfos2 =
[begin
ElemPath = [MsgName, FName],
TranslFn = gpb_gen_translators:find_translation(
ElemPath,
decode_init_default,
AnRes),
TrInitExpr = ?expr('Tr'('InitExpr', 'TrUserData'),
[replace_tree('InitExpr', InitExpr),
replace_term('Tr', TranslFn),
replace_tree('TrUserData', TrUserDataVar)]),
TrMOExpr = ?expr('Tr'('MOExpr', 'TrUserData'),
[replace_tree('MOExpr', MOExpr),
replace_term('Tr', TranslFn),
replace_tree('TrUserData', TrUserDataVar)]),
{FName, Presence, TrInitExpr, TrMOExpr}
end
|| {FName, Presence, InitExpr, MOExpr} <- ExprInfos1],
case gpb_lib:get_field_pass(MsgName, AnRes) of
pass_as_params ->
case MappingUnset of
records ->
[{FName, Expr} || {FName, _, Expr, _MOExpr} <- ExprInfos2];
#maps{unset_optional=present_undefined} ->
[{FName, Expr} || {FName, _, Expr, _MOExpr} <- ExprInfos2];
#maps{unset_optional=omitted} ->
[{FName, MapsOmittedExpr}
|| {FName, _, _Expr, MapsOmittedExpr} <- ExprInfos2]
end;
pass_as_record ->
case MappingUnset of
records ->
[{FName, Expr} || {FName, P, Expr, _} <- ExprInfos2,
P == m orelse P == d];
#maps{unset_optional=present_undefined} ->
[{FName, Expr} || {FName, _, Expr, _} <- ExprInfos2];
#maps{unset_optional=omitted} ->
[{FName, Expr} || {FName, m, _, Expr} <- ExprInfos2]
end
end.
is_msg_type({msg,_}) -> true;
is_msg_type(_) -> false.
%% @doc Generate an expression `#record{field_1 = R1, ... field_n=RN} = Msg',
%% and return it and also its constituent parts:
%% ```
%% {<the Msg var>,
%% <that expr>,
%% [{<name of field_1>, <the R_1 var>, repeated | required}, ...]}
%% '''
decoder_read_field_param(MsgName, MsgDef, Opts) ->
MappingVar = ?expr(Msg),
FFieldNamesOcc =
case proplists:get_bool(verify_decode_required_present, Opts) of
true -> required_or_repeated_field_names(MsgDef);
false -> repeated_field_names(MsgDef)
end,
FFields = [{FName, gpb_lib:var_n("R", I), Occ}
|| {I,{FName,Occ}} <- gpb_lib:index_seq(FFieldNamesOcc)],
FNameVars = [{FName, FVar} || {FName, FVar, _Occ} <- FFields],
FMatch = gpb_lib:record_match(MsgName, FNameVars),
FParam = ?expr('#r{f1=R1..fn=RN}' = '<Msg>',
[replace_tree('#r{f1=R1..fn=RN}', FMatch),
replace_tree('<Msg>', MappingVar)]),
{MappingVar, FParam, FFields}.
required_or_repeated_field_names(MsgDef) ->
[{FName, Occ} || #?gpb_field{name=FName, occurrence=Occ} <- MsgDef,
Occ == required orelse Occ == repeated].
repeated_field_names(MsgDef) ->
[{FName, Occ} || #?gpb_field{name=FName, occurrence=Occ} <- MsgDef,
Occ == repeated].
%% @doc Create expressions that check whether required fields
%% have been set, if the option indicates we should.
decoder_maybe_check_required_present(MsgName, FFields, Opts) ->
case proplists:get_bool(verify_decode_required_present, Opts) of
true ->
decoder_check_required_present(MsgName, FFields);
false ->
[]
end.
decoder_check_required_present(MsgName, FFields) ->
[?expr(if 'FVar' == undefined ->
error({gpb_error,
{decoding_failure,
{missing_required_msg_field,
'MsgName', 'FName'}}});
true ->
ok
end,
[replace_tree('FVar', FVar),
replace_term('MsgName', MsgName),
replace_term('FName', FName)])
|| {FName, FVar, required} <- FFields].
%% @doc Create a finalization expression by generating calls to
%% finalization translators. To give a notion what this is about,
%% a default such translator for repeated fields is `lists:reverse'.
decoder_finalize_result(MsgVar, FFields, MsgName, TrUserDataVar, AnRes) ->
gpb_lib:record_update(
MsgVar,
MsgName,
[begin
ElemPath = [MsgName, FName],
Finalizer = gpb_gen_translators:find_translation(
ElemPath,
decode_repeated_finalize,
AnRes),
FValueExpr = ?expr('lists:reverse'('<FVar>', 'TrUserData'),
[replace_term('lists:reverse',Finalizer),
replace_tree('<FVar>', FVar),
replace_tree('TrUserData',
TrUserDataVar)]),
{FName, FValueExpr}
end
|| {FName, FVar, repeated} <- FFields]).
%% @doc Compute optionality information for each field of a message.
calc_field_infos(MsgDef, Opts) ->
case gpb_lib:get_mapping_and_unset_by_opts(Opts) of
#maps{unset_optional=omitted, oneof=flat} ->
field_infos(MsgDef, true);
_ ->
field_infos(MsgDef, false)
end.
field_infos(MsgDef, FlattenOnoef) ->
[case Field of
#?gpb_field{name=FName, type={msg,_}} ->
{FName, optional};
#?gpb_field{name=FName, occurrence=defaulty} ->
%% On finalization, treat proto3 (scalar) fields as
%% required, to avoid redundant if F == '$undef' -> ...
%% checks; it can never be '$undef' since it has a
%% type-default. (except for sub messages and onoef)
{FName, required};
#?gpb_field{name=FName} ->
{FName, gpb_lib:get_field_occurrence(Field)};
#gpb_oneof{name=FName} ->
if not FlattenOnoef ->
{FName, optional};
FlattenOnoef ->
{FName, flatten_oneof}
end
end
|| Field <- MsgDef].
%% @doc Process a list of morping operation over a list of `#fn{}'s
%% (function with some associated meta info).
%%
%% A morphing operation is a fun that in some way or another change the
%% syntax tree of the function in the `#fn{}', but only for the `#fn{}'s
%% to which the morphing operation is applicable.
run_morph_ops([Op | Rest], Fns) ->
run_morph_ops(Rest, Op(Fns));
run_morph_ops([], Fns) ->
[gpb_codegen:erl_prettypr_format_nl(Tree) || #fn{tree=Tree} <- Fns].
%% @doc Replace unused function params and case clause patterns with
%% underscore.
%%
%% The intention is that code generation can issue match patterns for
%% all fields of a message, or all auxiliary working parameters and not
%% have to worry about underscoring unused ones to avoid warning messages
%% about unused variables.
underscore_unused_vars() ->
fun(Fns) ->
loop_fns(fun gpb_codemorpher:underscore_unused_vars/1,
process_all(),
Fns)
end.
%% @doc Passing and updating message fields as decoding-function
%% parameters instead of as a record/map. This often generates faster code.
%%
%% Applies to decoding-initialization functions only.
explode_param_init(MsgName, InitExprs, ArgPos) ->
fun(Fns) ->
loop_fns(
fun(FnTree) ->
gpb_codemorpher:explode_record_fields_to_params_init(
FnTree, ArgPos, {MsgName, InitExprs})
end,
process_initializer(),
Fns)
end.
%% @doc Passing and updating message fields as decoding-function
%% parameters instead of as a record/map. This often generates faster code.
%%
%% Applies to field decoding/passing functions only.
explode_param_pass(MsgName, FNames, ArgPos) ->
fun(Fns) ->
loop_fns(
fun(FnTree) ->
gpb_codemorpher:explode_record_fields_to_params(
FnTree, ArgPos, {MsgName, FNames})
end,
process_msg_passers(),
Fns)
end.
%% @doc For records, `undefined' is used to indicate an omitted optional value.
%% However, that atom is also a valid enum. For maps, we can do better,
%% so offer a way to change it to some other atom, such as `$undef', which is
%% not a valid protobuf enum.
change_undef_marker_in_clauses(Undef) ->
fun(Fns) ->
loop_fns(
fun(FnTree) ->
gpb_codemorpher:change_undef_marker_in_clauses(
FnTree, Undef)
end,
process_all(),
Fns)
end.
%% @doc The opposite of the {@link explode_param_init/3}, when a map is
%% constructed from all field parameters. Useful for maps with unset
%% optional = preset_undefined.
%%
%% Applies to finalization functions only.
implode_to_map_exprs_all_mandatory() ->
fun(Fns) ->
loop_fns(fun gpb_codemorpher:implode_to_map_expr/1,
process_finalizers(),
Fns)
end.
%% @doc The opposite of the {@link explode_param_init/3}, when a map is
%% constructed from all field parameters. Useful for maps with omitted
%% unset optionals (which is the default)
%%
%% Applies to finalization functions only.
implode_to_map_exprs(F1Pos, FieldInfos, Undef) ->
fun(Fns) ->
loop_fns(
fun(FnTree) ->
gpb_codemorpher:implode_to_map_exprs(
FnTree, F1Pos, FieldInfos, Undef)
end,
process_finalizers(),
Fns)
end.
%% @doc Change record expressions to map expressions. Useful when passing
%% messages as maps/records.
rework_records_to_maps(RecordParamPos, FieldInfos, Undef) ->
fun(Fns) ->
loop_fns(
fun(FnTree) ->
gpb_codemorpher:rework_records_to_maps(
FnTree, RecordParamPos, FieldInfos, Undef)
end,
process_initializers_finalizers_and_msg_passers(),
Fns)
end.
%% @doc Finalize expressions that have been marked as map expressions.
%%
%% Initially of importance but now of continuously diminising value, gpb
%% can generate code for maps also when hosted on a pre Erlang-17
%% systems where maps does not exist. It does this by rendering the map
%% expressions to string format using erl_syntax:text() nodes, which are
%% rendered in verbatim by the erl_prettypr:format machinery.
%%
%% However, the code morpher must still be able to manipulate
%% expressions. To make this possible, it uses marked record
%% expressions. The markup is specially tagged tuples. As the last
%% step, these tagged record expressions are converted to map
%% expressions, possibly by using erl_syntax text nodes if on old
%% systems, and otherwise map syntax nodes.
%%
%% That's what this morping step performs.
finalize_marked_map_exprs(Opts) ->
F = fun(MarkedExpr) ->
gpb_codemorpher:marked_map_expr_to_map_expr(MarkedExpr, Opts)
end,
fun(Fns) ->
loop_fns(F, process_initializers_finalizers_and_msg_passers(), Fns)
end.
loop_fns(MapFun, Filter, Fns) ->
[case matches_filter(Fn, Filter) of
true -> Fn#fn{tree = MapFun(FnTree)};
false -> Fn
end
|| #fn{tree=FnTree}=Fn <- Fns].
matches_filter(#fn{}=Fn, Filter) ->
Filter(Fn).
process_all() -> fun(#fn{}) -> true end.
process_initializer() -> fun(#fn{initializes_fields=Bool}) -> Bool end.
process_finalizers() -> fun(#fn{has_finalizer=Bool}) -> Bool end.
process_msg_passers() -> fun(#fn{passes_msg=Bool}) -> Bool end.
process_initializers_finalizers_and_msg_passers() ->
fun(#fn{initializes_fields=B1,
has_finalizer=B2,
passes_msg=B3}) ->
B1 or B2 or B3
end.