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