1%%% Copyright (C) 2017  Tomas Abrahamsson
2%%%
3%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
4%%%
5%%% This library is free software; you can redistribute it and/or
6%%% modify it under the terms of the GNU Lesser General Public
7%%% License as published by the Free Software Foundation; either
8%%% version 2.1 of the License, or (at your option) any later version.
9%%%
10%%% This library is distributed in the hope that it will be useful,
11%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
12%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13%%% Lesser General Public License for more details.
14%%%
15%%% You should have received a copy of the GNU Lesser General Public
16%%% License along with this library; if not, write to the Free Software
17%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18%%% MA  02110-1301  USA
19
20%%% @doc Helper functions for the code-generator module
21%%% @private
22
23-module(gpb_lib).
24
25-include("gpb_codegen.hrl").
26-include("gpb_compile.hrl").
27
28-export([mk_fn/2, mk_fn/3]).
29-export([replace_term/2]).
30-export([replace_tree/2]).
31-export([splice_trees/2]).
32-export([repeat_clauses/2]).
33
34-export([msgs_or_groups/1]).
35-export([msg_or_group_names/1]).
36-export([msg_names/1]).
37-export([contains_messages/1]).
38-export([get_field_name/1, get_field_names/1]).
39-export([get_field_rnum/1]).
40-export([get_field_occurrence/1]).
41-export([map_type_to_msg_name/2]).
42-export([unalias_enum/1]).
43-export([zip_for_non_opt_fields/2]).
44-export([any_field_is_sub_msg/1]).
45-export([any_field_is_repeated/1]).
46-export([any_enum_field_exists/1]).
47-export([any_packed_field_exists/1]).
48-export([at_least_one_submsg_with_size_not_known_at_compile_time_exists/1]).
49-export([get_field_pass/2]).
50-export([get_num_fields/2]).
51-export([is_packed/1]).
52-export([key_partition_on_optionality/2, key_partition_on_optionality/3]).
53-export([classify_field_merge_action/1]).
54-export([flatten_oneof_fields/1]).
55
56-export([fold_msg_fields/3]).
57-export([fold_msg_or_group_fields/3]).
58-export([fold_msgdef_fields/3]).
59-export([fold_msg_or_group_fields_o/3]).
60
61-export([mapping_match/3]).
62-export([mapping_create/3]).
63-export([mapping_create/4]).
64-export([mapping_update/4]).
65-export([record_match/2]).
66-export([record_create/2]).
67-export([record_update/3]).
68-export([map_match/2]).
69-export([map_create/2]).
70-export([map_set/3]).
71
72-export([get_2tuples_or_maps_for_maptype_fields_by_opts/1]).
73-export([get_records_or_maps_by_opts/1]).
74-export([get_mapping_and_unset_by_opts/1]).
75-export([get_strings_as_binaries_by_opts/1]).
76-export([get_type_specs_by_opts/1]).
77-export([get_gen_descriptor_by_opts/1]).
78-export([get_field_format_by_opts/1]).
79-export([mk_get_defs_as_maps_or_records_fn/1]).
80-export([get_defs_as_maps_or_records/1]).
81-export([get_epb_functions_by_opts/1]).
82-export([is_target_major_version_at_least/2]).
83-export([target_has_lists_join/1]).
84-export([target_has_variable_key_map_update/1]).
85-export([target_can_specify_map_item_presence_in_typespecs/1]).
86-export([target_can_do_flat_oneof_for_maps/1]).
87-export([target_may_fail_compilation_for_flat_oneof_for_maps/1]).
88-export([target_has_stacktrace_syntax/1]).
89-export([current_otp_release/0]).
90-export([proto2_type_default/3]).
91-export([proto3_type_default/3]).
92-export([get_maps_key_type_by_opts/1]).
93
94-export([var_f_n/1]).
95-export([var_b_n/1]).
96-export([var_n/2]).
97-export([var/2]).
98-export([prefix_var/2]).
99-export([assign_to_var/2]).
100-export([match_bind_var/2]).
101-export([varint_to_binary_fields/1]).
102-export([do_exprs/3]).
103
104-export([index_seq/1]).
105-export([smember/2, smember_any/2]).
106-export([indent/2, indent_lines/2]).
107-export([outdent_first/1]).
108-export([split_indent_iolist/2]).
109-export([split_indent_butfirst_iolist/2]).
110-export([cond_split_indent_iolist/3]).
111-export([iolist_to_utf8_or_escaped_binary/2]).
112-export([nowarn_unused_function/2]).
113-export([nowarn_dialyzer_attr/3]).
114
115-export([comma_join/1]).
116-export([nl_join/1]).
117-export([or_join/1]).
118-export([dot_join/1]).
119-export([is_substr/2]).
120-export([string_slice/2]).
121-export([string_lexemes/2]).
122-export([lowercase/1]).
123-export([uppercase/1]).
124-export([snake_case/1]).
125
126-include("../include/gpb.hrl").
127
128
129mk_fn(Prefix, Suffix) ->
130    list_to_atom(lists:concat([Prefix, Suffix])).
131
132mk_fn(Prefix, Middlefix, Suffix) when is_integer(Middlefix) ->
133    mk_fn(Prefix, list_to_atom(integer_to_list(Middlefix)), Suffix);
134mk_fn(Prefix, Middlefix, Suffix) ->
135    list_to_atom(lists:concat([Prefix, Middlefix, "_", Suffix])).
136
137%% Helpers for gpb_codegen parse tree transform operations -----------
138replace_term(Marker, NewTerm) when is_atom(Marker) ->
139    {replace_term, Marker, NewTerm}.
140
141replace_tree(Marker, NewTree) when is_atom(Marker) ->
142    {replace_tree, Marker, NewTree}.
143
144splice_trees(Marker, Trees) when is_atom(Marker) ->
145    {splice_trees, Marker, Trees}.
146
147repeat_clauses(Marker, RepetitionReplacements) ->
148    {repeat_clauses, Marker, RepetitionReplacements}.
149
150%% Various accessors -----
151
152msgs_or_groups(Defs) ->
153    [{Type,Name,Fields} || {{Type,Name},Fields} <- Defs,
154                           Type =:= msg orelse Type =:= group].
155
156msg_or_group_names(Defs) ->
157    [Name || {_Type, Name, _Fields} <- msgs_or_groups(Defs)].
158
159msg_names(Defs) ->
160    [Name || {{msg, Name}, _Fields} <- Defs].
161
162contains_messages(Defs) ->
163    lists:any(fun({{msg, _}, _}) -> true;
164                 (_)             -> false
165              end,
166              Defs).
167
168get_field_names(MsgDef) ->
169    [get_field_name(Field) || Field <- MsgDef].
170
171get_field_name(#?gpb_field{name=FName}) -> FName;
172get_field_name(#gpb_oneof{name=FName})  -> FName.
173
174get_field_rnum(#?gpb_field{rnum=RNum}) -> RNum;
175get_field_rnum(#gpb_oneof{rnum=RNum})  -> RNum.
176
177get_field_occurrence(#?gpb_field{occurrence=Occurrence}) -> Occurrence;
178get_field_occurrence(#gpb_oneof{})                       -> optional.
179
180map_type_to_msg_name(KeyType, {msg,MsgName}) ->
181    list_to_atom(?ff("map<~s,~s>", [KeyType, MsgName]));
182map_type_to_msg_name(KeyType, {enum,EnumName}) ->
183    list_to_atom(?ff("map<~s,~s>", [KeyType, EnumName]));
184map_type_to_msg_name(KeyType, ValueType) ->
185    list_to_atom(?ff("map<~s,~s>", [KeyType, ValueType])).
186
187%% The "option allow_alias = true;" inside an enum X { ... }
188%% says it is ok to have multiple symbols that map to the same numeric value.
189%% Appeared in protobuf 2.5.0.
190unalias_enum([{_Sym,Value}=Enum | Rest]) ->
191    [Enum | unalias_enum([E || {_,V}=E <- Rest, V /= Value])];
192unalias_enum([{option,_Name,_Value} | Rest]) ->
193    unalias_enum(Rest);
194unalias_enum([]) ->
195    [].
196
197zip_for_non_opt_fields([#?gpb_field{name=FName,
198                                    occurrence=Occurrence} | FRest],
199                       [Elem | ERest]) ->
200    case Occurrence of
201        optional -> zip_for_non_opt_fields(FRest, ERest);
202        required -> [{FName, Elem} | zip_for_non_opt_fields(FRest, ERest)];
203        repeated -> zip_for_non_opt_fields(FRest, ERest)
204    end;
205zip_for_non_opt_fields([#gpb_oneof{} | FRest], [_Elem | ERest]) ->
206    zip_for_non_opt_fields(FRest, ERest);
207zip_for_non_opt_fields([], []) ->
208    [].
209
210any_field_is_sub_msg(Fields) ->
211    lists:any(fun(#?gpb_field{type={msg,_}}) -> true;
212                 (#?gpb_field{type={group,_}}) -> true;
213                 (#?gpb_field{type={map,_,_}}) -> true;
214                 (#gpb_oneof{fields=Fs}) -> any_field_is_sub_msg(Fs);
215                 (_) -> false
216              end,
217              Fields).
218
219any_field_is_repeated(Fields) ->
220    lists:any(fun(#?gpb_field{occurrence=Occ}) -> Occ == repeated;
221                 (#gpb_oneof{}) -> false
222              end,
223              Fields).
224
225any_enum_field_exists(UsedTypes) ->
226    sets:fold(fun({enum,_}, _Acc) -> true;
227                 (_, Acc)         -> Acc
228              end,
229              false,
230              UsedTypes).
231
232any_packed_field_exists(#anres{num_packed_fields=0}) -> false;
233any_packed_field_exists(#anres{num_packed_fields=_}) -> true.
234
235at_least_one_submsg_with_size_not_known_at_compile_time_exists(AnRes) ->
236    #anres{used_types=UsedTypes,
237           maps_as_msgs=MapsAsMsgs,
238           known_msg_size=KnownSize} = AnRes,
239    SubMsgNames = [MsgName || {msg,MsgName} <- sets:to_list(UsedTypes)],
240    MapMsgNames = [MsgName || {{msg,MsgName},_} <- MapsAsMsgs],
241    IsMsgSizeUnknown = fun(Nm) -> dict:fetch(Nm, KnownSize) == undefined end,
242    lists:any(IsMsgSizeUnknown, SubMsgNames) orelse
243        lists:any(IsMsgSizeUnknown, MapMsgNames).
244
245get_field_pass(MsgName, #anres{d_field_pass_method=D}) ->
246    dict:fetch(MsgName, D).
247
248get_num_fields(MsgName, #anres{num_fields=D}) ->
249    dict:fetch(MsgName, D).
250
251is_packed(#?gpb_field{type=Type, opts=Opts}) ->
252    gpb:is_type_packable(Type) andalso lists:member(packed, Opts).
253
254is_maptype_field(#?gpb_field{type={map,_,_}}) -> true;
255is_maptype_field(_) -> false.
256
257%% -> {Optionals, NonOptionals}
258key_partition_on_optionality(Key, Items) ->
259    key_partition_on_optionality(Key, Items, []).
260key_partition_on_optionality(Key, Items, Opts) ->
261    lists:partition(
262      fun(Item) ->
263              Field = element(Key, Item),
264              case get_field_occurrence(Field) of
265                  optional -> true;
266                  required -> false;
267                  repeated -> case mapfields_considered_required(Opts) of
268                                  false -> true;
269                                  true  -> not is_maptype_field(Field)
270                              end
271              end
272      end,
273      Items).
274
275mapfields_considered_required(Opts) ->
276    proplists:get_bool(mapfields_are_required, Opts).
277
278classify_field_merge_action(FieldDef) ->
279    case FieldDef of
280        #?gpb_field{occurrence=required, type={msg, _}}   -> msgmerge;
281        #?gpb_field{occurrence=optional, type={msg, _}}   -> msgmerge;
282        #?gpb_field{occurrence=required, type={group, _}} -> msgmerge;
283        #?gpb_field{occurrence=optional, type={group, _}} -> msgmerge;
284        #?gpb_field{occurrence=required}                  -> overwrite;
285        #?gpb_field{occurrence=optional}                  -> overwrite;
286        #?gpb_field{occurrence=repeated}                  -> seqadd
287    end.
288
289flatten_oneof_fields([#?gpb_field{}=F | Rest]) ->
290    [F | flatten_oneof_fields(Rest)];
291flatten_oneof_fields([#gpb_oneof{fields=OFields} | Rest]) ->
292    OFields ++ flatten_oneof_fields(Rest);
293flatten_oneof_fields([]) ->
294    [].
295
296%% Msg iteration --------
297
298%% Loop over all message fields, including oneof-fields
299%% Call Fun for all #?gpb_fields{}, skip over non-msg defs
300fold_msg_fields(Fun, InitAcc, Defs) ->
301    fold_msg_fields_o(
302      fun(MsgName, Field, _IsOneOf, Acc) -> Fun(MsgName, Field, Acc) end,
303      InitAcc,
304      Defs).
305
306fold_msg_or_group_fields(Fun, InitAcc, Defs) ->
307    fold_msg_or_group_fields_o(
308      fun(Type, Name, Field, _IsOneOf, Acc) -> Fun(Type, Name, Field, Acc) end,
309      InitAcc,
310      Defs).
311
312fold_msgdef_fields(Fun, InitAcc, Fields) ->
313    fold_msgdef_fields_o(
314      fun(Field, _IsOneOf, Acc) -> Fun(Field, Acc) end,
315      InitAcc,
316      Fields).
317
318%% The fun takes 4 args: Fun(Msgname, #?gpb_field{}, IsOneof, Acc) -> Acc1
319fold_msg_fields_o(Fun, InitAcc, Defs) ->
320    lists:foldl(
321      fun({{msg, MsgName}, Fields}, Acc) ->
322              FFun = fun(Field, IsOneOf, FAcc) ->
323                             Fun(MsgName, Field, IsOneOf, FAcc)
324                     end,
325              fold_msgdef_fields_o(FFun, Acc, Fields);
326         (_Def, Acc) ->
327              Acc
328      end,
329      InitAcc,
330      Defs).
331
332%% The fun takes 5 args:
333%% Fun(msg | group, Name, #?gpb_field{}, IsOneof, Acc) -> Acc1
334fold_msg_or_group_fields_o(Fun, InitAcc, Defs) ->
335    lists:foldl(
336      fun({{Type, Name}, Fields}, Acc) when Type =:= msg;
337                                            Type =:= group ->
338              FFun = fun(Field, IsOneOf, FAcc) ->
339                             Fun(Type, Name, Field, IsOneOf, FAcc)
340                     end,
341              fold_msgdef_fields_o(FFun, Acc, Fields);
342         (_Def, Acc) ->
343              Acc
344      end,
345      InitAcc,
346      Defs).
347
348
349
350%% The fun takes 3 args: Fun(#?gpb_field{}, IsOneof, Acc) -> Acc1
351fold_msgdef_fields_o(Fun, InitAcc, Fields) ->
352    lists:foldl(
353      fun(#?gpb_field{}=Field, Acc) ->
354              Fun(Field, false, Acc);
355         (#gpb_oneof{name=CFName, fields=OFields}, Acc) ->
356              IsOneOf = {true, CFName},
357              lists:foldl(fun(OField, OAcc) -> Fun(OField, IsOneOf, OAcc) end,
358                          Acc,
359                          OFields)
360      end,
361      InitAcc,
362      Fields).
363
364%% Record or map expr helpers --------
365
366%% a mapping is either a record or a map
367%%
368%%
369mapping_match(RName, Fields, Opts) ->
370    case get_records_or_maps_by_opts(Opts) of
371        records -> record_match(RName, Fields);
372        maps    -> map_match(Fields, Opts)
373    end.
374
375mapping_create(RName, Fields, Opts) when is_list(Opts) ->
376    Fn = fun() -> get_records_or_maps_by_opts(Opts) end,
377    mapping_create(RName, Fields, Fn, Opts).
378
379mapping_create(RName, Fields, RecordsOrMaps, Opts)
380  when is_function(RecordsOrMaps) ->
381    case RecordsOrMaps() of
382        records -> record_create(RName, Fields);
383        maps    -> map_create(Fields, Opts)
384    end.
385
386mapping_update(Var, RName, FieldsValues, Opts) ->
387    case get_records_or_maps_by_opts(Opts) of
388        records ->
389            record_update(Var, RName, FieldsValues);
390        maps ->
391            case get_mapping_and_unset_by_opts(Opts) of
392                #maps{unset_optional=present_undefined} ->
393                    map_update(Var, FieldsValues, Opts);
394                #maps{unset_optional=omitted} ->
395                    map_set(Var, FieldsValues, Opts)
396            end
397    end.
398
399%% records
400record_match(RName, Fields) -> record_create_or_match(RName, Fields).
401record_create(RName, Fields) -> record_create_or_match(RName, Fields).
402
403record_create_or_match(RecordName, FieldsValueTrees) ->
404    record_update(none, RecordName, FieldsValueTrees).
405
406record_update(Var, _RecordName, []) when Var /= none ->
407    %% No updates to be made, maybe no fields
408    Var;
409record_update(Var, RecordName, FieldsValueTrees) ->
410    erl_syntax:record_expr(
411      Var,
412      erl_syntax:atom(RecordName),
413      [erl_syntax:record_field(erl_syntax:atom(FName), ValueSyntaxTree)
414       || {FName, ValueSyntaxTree} <- FieldsValueTrees]).
415
416%% maps
417-ifndef(NO_HAVE_MAPS).
418map_match(Fields, Opts) ->
419    Literal = mapkey_literal_by_opts(Opts),
420    erl_syntax:map_expr(
421      [erl_syntax:map_field_exact(Literal(FName), Expr)
422       || {FName, Expr} <- Fields]).
423
424map_create(Fields, Opts) ->
425    map_set(none, Fields, Opts).
426
427map_update(Var, [], _Opts) when Var /= none ->
428    %% No updates to be made, maybe no fields
429    Var;
430map_update(Var, FieldsValueTrees, Opts) ->
431    Literal = mapkey_literal_by_opts(Opts),
432    erl_syntax:map_expr(
433      Var,
434      [erl_syntax:map_field_exact(Literal(FName), Expr)
435       || {FName, Expr} <- FieldsValueTrees]).
436
437map_set(Var, [], _Opts) when Var /= none ->
438    %% No updates to be made, maybe no fields
439    Var;
440map_set(Var, FieldsValueTrees, Opts) ->
441    Literal = mapkey_literal_by_opts(Opts),
442    ExprF = mapkey_expr_by_opts(Opts),
443    erl_syntax:map_expr(
444      Var,
445      [if is_atom(FName) ->
446               erl_syntax:map_field_assoc(Literal(FName), Expr);
447          true -> % Key can be a variable or other type too.
448               erl_syntax:map_field_assoc(ExprF(FName), Expr)
449       end
450       || {FName, Expr} <- FieldsValueTrees]).
451
452mapkey_literal_by_opts(Opts) ->
453    case get_maps_key_type_by_opts(Opts) of
454        atom ->
455            fun(Atom) -> erl_syntax:atom(Atom) end;
456        binary ->
457            fun(Atom) ->
458                    erl_syntax:binary(
459                           [erl_syntax:binary_field(
460                              erl_syntax:string(atom_to_list(Atom)))])
461            end
462    end.
463
464mapkey_expr_by_opts(Opts) ->
465    case get_maps_key_type_by_opts(Opts) of
466        atom ->
467            fun(Expr) -> Expr end;
468        binary ->
469            fun(Expr) ->
470                    erl_syntax:binary(
471                      [erl_syntax:binary_field(
472                         erl_syntax:application(erl_syntax:atom(erlang),
473                                                erl_syntax:atom(atom_to_list),
474                                                [Expr]))])
475            end
476    end.
477
478-else. %% on a pre Erlang 17 system
479
480map_match(Fields, Opts) ->
481    KVs = case get_maps_key_type_by_opts(Opts) of
482              atom ->
483                  [?ff("~p := ~s", [FName, Var])
484                   || {FName, Var} <- map_kvars(Fields)];
485              binary ->
486                  [?ff("<<\"~s\">> := ~s", [FName, Var])
487                   || {FName, Var} <- map_kvars(Fields)]
488          end,
489    erl_syntax:text(?ff("#{~s}", [string:join(KVs, ", ")])).
490
491map_create(Fields, Opts) ->
492    KVs = case get_maps_key_type_by_opts(Opts) of
493              atom ->
494                  [?ff("~p => ~s", [FName, Val])
495                   || {FName, Val} <- map_kvalues(Fields)];
496              binary ->
497                  [?ff("<<\"~s\">> => ~s", [FName, Val])
498                   || {FName, Val} <- map_kvalues(Fields)]
499          end,
500    erl_syntax:text(?ff("#{~s}", [string:join(KVs, ", ")])).
501
502map_update(Var, [], _Opts) when Var /= none ->
503    %% No updates to be made, maybe no fields
504    Var;
505map_update(Var, FieldsValueTrees, Opts) ->
506    KVs = case get_maps_key_type_by_opts(Opts) of
507              atom ->
508                  [?ff("~p := ~s", [FName, Val])
509                   || {FName, Val} <- map_kvalues(FieldsValueTrees)];
510              binary ->
511                  [?ff("<<\"~s\">> := ~s", [FName, Val])
512                   || {FName, Val} <- map_kvalues(FieldsValueTrees)]
513          end,
514    erl_syntax:text(?ff("~s#{~s}", [var_literal(Var), string:join(KVs, ", ")])).
515
516
517map_set(Var, [], _Opts) when Var /= none ->
518    %% No updates to be made, maybe no fields
519    Var;
520map_set(Var, FieldsValueTrees, Opts) ->
521    KVs = case get_maps_key_type_by_opts(Opts) of
522              atom ->
523                  [?ff("~p => ~s", [FName, Val])
524                   || {FName, Val} <- map_kvalues(FieldsValueTrees)];
525              binary ->
526
527                  [?ff("<<\"~s\">> => ~s", [FName, Val])
528                   || {FName, Val} <- map_kvalues(FieldsValueTrees)]
529          end,
530    erl_syntax:text(?ff("~s#{~s}", [var_literal(Var), string:join(KVs, ", ")])).
531
532
533%% -> [{atom(), string()}]
534map_kvars(KVars) ->
535    [{Key, var_literal(Var)} || {Key, Var} <- KVars].
536
537var_literal(Var) ->
538    variable = erl_syntax:type(Var),
539    erl_syntax:variable_literal(Var).
540
541%% -> [{atom(), string()}]
542map_kvalues(KVars) ->
543    [begin
544         ExprAsStr = erl_prettypr:format(Expr),
545         {Key, ExprAsStr}
546     end
547     || {Key, Expr} <- KVars].
548
549-endif. %% NO_HAVE_MAPS
550
551%% Option helpers ---------------
552
553get_2tuples_or_maps_for_maptype_fields_by_opts(Opts) ->
554    Default = false,
555    case proplists:get_value(mapfields_as_maps, Opts, Default) of
556        true  -> maps;
557        false -> '2tuples'
558    end.
559
560get_records_or_maps_by_opts(Opts) ->
561    Default = false,
562    case proplists:get_value(msgs_as_maps, Opts, Default) of
563        false -> records;
564        true  -> maps
565    end.
566
567get_mapping_and_unset_by_opts(Opts) ->
568    case get_records_or_maps_by_opts(Opts) of
569        records ->
570            records;
571        maps ->
572            DefaultUnsetOptional = omitted,
573            UnseOptional = proplists:get_value(maps_unset_optional, Opts,
574                                               DefaultUnsetOptional),
575            Oneof = proplists:get_value(maps_oneof, Opts, tuples),
576            #maps{unset_optional=UnseOptional, oneof=Oneof}
577    end.
578
579get_strings_as_binaries_by_opts(Opts) ->
580    proplists:get_bool(strings_as_binaries, Opts).
581
582get_type_specs_by_opts(Opts) ->
583    Default = true,
584    proplists:get_value(type_specs, Opts, Default).
585
586get_gen_descriptor_by_opts(Opts) ->
587    proplists:get_bool(descriptor, Opts).
588
589get_field_format_by_opts(Opts) ->
590    case proplists:get_bool(defs_as_proplists, proplists:unfold(Opts)) of
591        false -> %% default
592            case get_defs_as_maps_or_records(Opts) of
593                records -> fields_as_records;
594                maps    -> fields_as_maps
595            end;
596        true ->
597            fields_as_proplists
598    end.
599
600mk_get_defs_as_maps_or_records_fn(Opts) ->
601    fun() -> get_defs_as_maps_or_records(Opts) end.
602
603get_defs_as_maps_or_records(Opts) ->
604    Default = false,
605    case proplists:get_value(defs_as_maps, Opts, Default) of
606        false -> records;
607        true  -> maps
608    end.
609
610get_epb_functions_by_opts(Opts) ->
611    proplists:get_bool(epb_functions, Opts).
612
613is_target_major_version_at_least(VsnMin, Opts) ->
614    case proplists:get_value(target_erlang_version, Opts, current) of
615        current ->
616            is_current_major_version_at_least(VsnMin);
617        N when is_integer(N) ->
618            N >= VsnMin
619    end.
620
621is_current_major_version_at_least(VsnMin) ->
622    current_otp_release() >= VsnMin.
623
624current_otp_release() ->
625    case erlang:system_info(otp_release) of
626        "R"++Rest -> % R16 or ealier
627            FirstChunkOfDigits = lists:takewhile(fun is_digit/1, Rest),
628            list_to_integer(FirstChunkOfDigits);
629        RelStr ->
630            %% In Erlang 17 the leading "R" was dropped
631            %% The exact format isn't super documented,
632            %% so be prepared for some (future?) alternatives.
633            try list_to_integer(RelStr) of
634                N when is_integer(N) -> N
635            catch error:badarg ->
636                    Rel = lists:dropwhile(fun is_not_digit/1, RelStr),
637                    FirstChunkOfDigits = lists:takewhile(fun is_digit/1, Rel),
638                    list_to_integer(FirstChunkOfDigits)
639            end
640    end.
641
642is_not_digit(C) -> not is_digit(C).
643
644is_digit(C) when $0 =< C, C =< $9 -> true;
645is_digit(_) -> false.
646
647%% Whether target version has the function lists:join/2.
648target_has_lists_join(Opts) ->
649    is_target_major_version_at_least(19, Opts).
650
651%% Whether target version supports M#{K => V} when K is a variable.
652%% If before this support was added, one must use maps:put(K, V, M) instead.
653target_has_variable_key_map_update(Opts) ->
654    is_target_major_version_at_least(18, Opts).
655
656%% Whether target version supports #{key := type()} type spec syntax.
657%% In Erlang 19, := indicates mandatory presence and => optional presence.
658%% In Erlang 18, only => was supported.
659target_can_specify_map_item_presence_in_typespecs(Opts) ->
660    is_target_major_version_at_least(19, Opts).
661
662target_can_do_flat_oneof_for_maps(Opts) ->
663    %% Not possible in Erlang 17 because:
664    %%    Variables as map keys appeared in 18.0. In 17, supports only literals
665    %%    as map keys.
666    is_target_major_version_at_least(18, Opts).
667
668target_may_fail_compilation_for_flat_oneof_for_maps(Opts) ->
669    %% In Erlang 18.3.4.6 .. 18.3.4.9
670    %% (ie the currently last/highest 4 Erlang 18 versions) this happens:
671    %% --
672    %%    % erlc <erl for flat oneof>.erl
673    %%    beamvalidatorerror: function v_msg_m1/3+75:
674    %%      Internal consistency check failed - please report this bug.
675    %%      Instruction: {move,{x,2},{y,0}}
676    %%      Error:       {uninitialized_reg,{x,2}}:
677    %% --
678    %% (introduced in c803276c9)
679    %% All Erlang 19 versions and later seems fine.
680    case target_can_do_flat_oneof_for_maps(Opts) of
681        true ->
682            AtLeast19 = is_target_major_version_at_least(19, Opts),
683            AtLeast18 = is_target_major_version_at_least(18, Opts),
684            AtLeast18 andalso (not AtLeast19);
685        false ->
686            true % On pre-18, it will definitely fail
687    end.
688
689%% In Erlang 21, the function erlang:get_stacktrace/0 was deprecated
690%% and there is new syntax for retrieving the stacktrace:
691%%
692%%   try ...
693%%   catch Class:Reason:Stacktrace -> ...
694%%   end
695target_has_stacktrace_syntax(Opts) ->
696    is_target_major_version_at_least(21, Opts).
697
698proto2_type_default(Type, Defs, Opts) ->
699    type_default(Type, Defs, Opts, fun gpb:proto2_type_default/2).
700
701proto3_type_default(Type, Defs, Opts) ->
702    type_default(Type, Defs, Opts, fun gpb:proto3_type_default/2).
703
704type_default(Type, Defs, Opts, GetTypeDefault) ->
705    if Type == string ->
706            case get_strings_as_binaries_by_opts(Opts) of
707                true ->
708                    list_to_binary(GetTypeDefault(Type, Defs));
709                false ->
710                    GetTypeDefault(Type, Defs)
711            end;
712       Type /= string ->
713            GetTypeDefault(Type, Defs)
714    end.
715
716get_maps_key_type_by_opts(Opts) ->
717    proplists:get_value(maps_key_type, Opts, atom).
718
719%% Syntax tree stuff ----
720
721var_f_n(N) -> var_n("F", N).
722var_b_n(N) -> var_n("B", N).
723
724var_n(S, N) ->
725    var("~s~w", [S, N]).
726
727var(Fmt, Args) ->
728    erl_syntax:variable(?ff(Fmt, Args)).
729
730prefix_var(Prefix, Var) ->
731    erl_syntax:variable(Prefix ++ erl_syntax:variable_literal(Var)).
732
733match_bind_var(Pattern, Var) ->
734    ?expr('Pattern' = 'Var',
735          [replace_tree('Pattern', Pattern),
736           replace_tree('Var', Var)]).
737
738assign_to_var(Var, Expr) ->
739    ?expr('<Var>' = '<Expr>',
740          [replace_tree('<Var>', Var),
741           replace_tree('<Expr>', Expr)]).
742
743varint_to_binary_fields(IntValue) ->
744    [erl_syntax:binary_field(?expr('<n>', [replace_term('<n>', N)]), [])
745     || N <- binary_to_list(gpb:encode_varint(IntValue))].
746
747%% Given a sequence, `Seq', of expressions, and an initial expression,
748%% Construct:
749%%     TmpVar1 = InitialExpr,
750%%     TmpVar2 = <1st expression in sequence, possibly involving TmpVar1>
751%%     TmpVar3 = <2st expression in sequence, possibly involving TmpVar2>
752%%     ...
753%%     <final expression in sequence, possibly involving TmpVarN-1>
754do_exprs(F, InitExpr, Seq) ->
755    {LastExpr, ExprsReversed, _N} =
756        lists:foldl(
757          fun(Elem, {PrevE,Es,N}) ->
758                  Var = var_n("S", N),
759                  BoundPrevE = assign_to_var(Var, PrevE),
760                  E = F(Elem, Var),
761                  {E, [BoundPrevE | Es], N+1}
762          end,
763          {InitExpr, [], 1},
764          Seq),
765    lists:reverse([LastExpr | ExprsReversed]).
766
767%% Misc ---
768
769index_seq([]) -> [];
770index_seq(L)  -> lists:zip(lists:seq(1,length(L)), L).
771
772smember(Elem, Set) -> %% set-member
773    sets:is_element(Elem, Set).
774
775smember_any(Elems, Set) -> %% is any elem a member in the set
776    lists:any(fun(Elem) -> smember(Elem, Set) end,
777              Elems).
778
779indent(Indent, Str) ->
780    lists:duplicate(Indent, $\s) ++ Str.
781
782outdent_first(IoList) ->
783    lists:dropwhile(fun(C) -> C == $\s end,
784                    binary_to_list(iolist_to_binary(IoList))).
785
786indent_lines(Indent, Lines) ->
787    [indent(Indent, Line) || Line <- Lines].
788
789split_indent_butfirst_iolist(Indent, IoList) ->
790    strip_left(iolist_to_binary(split_indent_iolist(Indent, IoList))).
791
792strip_left(<<" ", Rest/binary>>) -> strip_left(Rest);
793strip_left(Other)                -> Other.
794
795cond_split_indent_iolist(Condition, Indent, IoList) ->
796    B = iolist_to_binary(IoList),
797    case Condition(B) of
798        true  -> split_indent_iolist(Indent, IoList);
799        false -> B
800    end.
801
802split_indent_iolist(Indent, IoList) ->
803    [if Line == <<>> -> "\n"; %% don't indent empty lines
804        true -> [indent(Indent, Line), "\n"]
805     end
806     || Line <- linesplit_iolist(IoList)].
807
808linesplit_iolist(Iolist) ->
809    re:split(Iolist, ["\n"], [trim, {return,binary}]).
810
811iolist_to_utf8_or_escaped_binary(IoList, Opts) ->
812    case understands_coding(Opts) of
813        true  ->
814            unicode:characters_to_binary(
815              ["%% -*- coding: utf-8 -*-\n",
816               IoList]);
817        false ->
818            %% What to do if on Erlang R15 or earlier?  We can't utf8-encode
819            %% the file, because Erlang R15 will read it as latin1.
820            %%
821            %% For now, Assume such encodings are in strings only.
822            %% So far, this is safe, since neither message names nor field
823            %% names nor enum symbols are allowed to be non-ascii.
824            %%
825            %% This means only place for non-ascii is in comments and
826            %% in default strings. Hope I haven't overlooked some
827            %% important place...
828            iolist_to_binary(esc_non_ascii(IoList))
829    end.
830
831understands_coding(Opts) ->
832    %% version   coding: X             default source encoding
833    %% R15:      ignores               latin1
834    %% R16:      understands           latin1
835    %% 17:       understands           utf-8
836    is_target_major_version_at_least(16, Opts).
837
838esc_non_ascii([H|T]) -> [esc_non_ascii(H) | esc_non_ascii(T)];
839esc_non_ascii([])    -> [];
840esc_non_ascii(B) when is_binary(B) -> B;
841esc_non_ascii(C) when is_integer(C), C =< 127 -> C;
842esc_non_ascii(C) when is_integer(C), C > 127  -> ?f("\\x{~.16b}", [C]).
843
844nowarn_dialyzer_attr(FnName,Arity,Opts) ->
845    %% Especially for the verifiers, dialyzer's success typing can
846    %% think that some code paths in the verifiers can't be reached,
847    %% and in a sense, it is right: the verifiers do much the same
848    %% work as dialyzer. But I think their existence is still
849    %% warranted because (a) they work-time rather than compile-time,
850    %% and (b) provide for shorter turn-around times when dialyzer
851    %% can take some time to analyze a non-trivial proto file.
852    %%
853    %% So mute dialyzer for the verifier functions.
854    case can_do_dialyzer_attr(Opts) of
855        true ->
856            ?f("-dialyzer({nowarn_function,~p/~w}).~n", [FnName,Arity]);
857        false ->
858            %% Too old system (Erlang 17 or older), which will see
859            %% the dialyzer attr as just another plain attr,
860            %% which must be located before all functions.
861            %% Just don't silence dialyzer on these systems.
862            ""
863    end.
864
865can_do_dialyzer_attr(Opts) ->
866    is_target_major_version_at_least(18, Opts).
867
868nowarn_unused_function(FnName, Arity) ->
869    ?f("-compile({nowarn_unused_function,~p/~w}).~n", [FnName,Arity]).
870
871-ifndef(NO_HAVE_ERL20_STR_FUNCTIONS).
872
873comma_join(Elements) ->
874    lists:append(lists:join(", ", Elements)).
875
876nl_join(Elements) ->
877    lists:append(lists:join("\n", Elements)).
878
879or_join(Alternatives) ->
880    lists:append(lists:join(" | ", Alternatives)).
881
882dot_join(Alternatives) ->
883    lists:append(lists:join(".", Alternatives)).
884
885is_substr(SearchPattern, String) ->
886    string:find(String, SearchPattern) /= nomatch.
887
888string_slice(String, Start) ->
889    string:slice(String, Start).
890
891string_lexemes(String, Separators) ->
892    string:lexemes(String, Separators).
893
894lowercase(Str) ->
895    string:lowercase(Str).
896
897uppercase(Str) ->
898    string:uppercase(Str).
899
900-else.  % NO_HAVE_ERL20_STR_FUNCTIONS
901
902comma_join(Elements) ->
903    string:join(Elements, ", ").
904
905nl_join(Elements) ->
906    string:join(Elements, "\n").
907
908or_join(Alternatives) ->
909    string:join(Alternatives, " | ").
910
911dot_join(Alternatives) ->
912    string:join(Alternatives, ".").
913
914is_substr(SearchPattern, String) ->
915    string:str(String, SearchPattern) > 0.
916
917string_slice(String, Start0) ->
918    string:substr(String, Start0 + 1).
919
920string_lexemes(String, Separators) ->
921    string:tokens(String, Separators).
922
923lowercase(Str) ->
924    string:to_lower(Str).
925
926uppercase(Str) ->
927    string:to_upper(Str).
928
929-endif. % NO_HAVE_ERL20_STR_FUNCTIONS
930
931snake_case(Str) ->
932    lowercase(
933      lists:foldl(fun(RE, Snaking) ->
934                          re:replace(Snaking, RE, "\\1_\\2", [{return, list},
935                                                              global])
936                  end, Str, [%% uppercase followed by lowercase
937                             "([^.])([A-Z][a-z]+)",
938                             %% any consecutive digits
939                             "([^.])([0-9]+)",
940                             %% uppercase with lowercase
941                             %% or digit before it
942                             "([a-z0-9])([A-Z])"])).
943