123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719(*
The MIT License (MIT)
Copyright (c) 2016 Maxime Ransan <maxime.ransan@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*)moduleE=Pb_exceptionmoduleOt=Pb_codegen_ocaml_typemoduleTt=Pb_typing_type_treemoduleTyping_util=Pb_typing_util(** [rev_split_by_naming_convention s] will split [s] according to the protobuf
coding style convention. The rule split are
{ul
{- character ['_'] is a separator}
{- the first uppercase letter after a lower case is a separator
(ie FooBar will be split into [ ["Bar";"Foo"] ]}
}
*)letrev_split_by_naming_conventions=letis_uppercasec=64<Char.codec&&Char.codec<91inletadd_sub_stringstart_iend_il=ifstart_i=end_ithenlelseString.subsstart_i(end_i-start_i)::linletl,start_i,_=Pb_util.string_fold_lefti(funaccic->letl,start_i,uppercase_run=accinmatchc,uppercase_runwith|'_',_->add_sub_stringstart_iil,i+1,false|c,falsewhenis_uppercasec->add_sub_stringstart_iil,i,true|_->l,start_i,is_uppercasec)([],0,false)sinletlen=String.lengthsinadd_sub_stringstart_ilenlletfix_ocaml_keyword_conflicts=matchswith|"and"|"as"|"assert"|"begin"|"class"|"constraint"|"do"|"done"|"downto"|"else"|"end"|"exception"|"external"|"false"|"for"|"fun"|"function"|"functor"|"if"|"in"|"include"|"inherit"|"initializer"|"lazy"|"let"|"match"|"method"|"module"|"mutable"|"new"|"nonrec"|"object"|"of"|"open"|"or"|"private"|"rec"|"sig"|"struct"|"then"|"to"|"true"|"try"|"type"|"unit"|"val"|"virtual"|"when"|"while"|"with"|"mod"|"land"|"lor"|"lxor"|"lsl"|"lsr"|"asr"->s^"_"|_->sletconstructor_names=rev_split_by_naming_conventions|>List.rev|>String.concat"_"|>String.lowercase_ascii|>String.capitalize_asciiletmodule_name=constructor_nameletlabel_name_of_field_names=rev_split_by_naming_conventions|>List.rev|>String.concat"_"|>String.lowercase_ascii|>fix_ocaml_keyword_conflictletmodule_prefix_of_file_namefile_name=letfile_name=Filename.basenamefile_nameinmatchString.rindexfile_name'.'with|dot_index->module_name@@String.subfile_name0dot_index|exceptionNot_found->E.invalid_file_namefile_namelettype_namemessage_scopename:string=letmoduleS=Stringinletall_names=message_scope@[name]inletall_names=List.map(funs->rev_split_by_naming_conventions|>List.rev|>List.mapString.lowercase_ascii)all_namesinletall_names=List.flattenall_namesinmatchall_nameswith|[]->failwith"Programmatic error"|hd::[]->fix_ocaml_keyword_conflicthd|_->S.concat"_"all_namesletwrapper_type_of_type_name=function|"FloatValue"->Ot.{wt_type=Bt_float;wt_pk=Pk_bits32}|"DoubleValue"->Ot.{wt_type=Bt_float;wt_pk=Pk_bits64}|"Int64Value"->Ot.{wt_type=Bt_int64;wt_pk=Pk_varintfalse}|"UInt64Value"->Ot.{wt_type=Bt_int64;wt_pk=Pk_varintfalse}|"Int32Value"->Ot.{wt_type=Bt_int32;wt_pk=Pk_varintfalse}|"UInt32Value"->Ot.{wt_type=Bt_int32;wt_pk=Pk_varintfalse}|"BoolValue"->Ot.{wt_type=Bt_bool;wt_pk=Pk_varintfalse}|"StringValue"->Ot.{wt_type=Bt_string;wt_pk=Pk_bytes}|"BytesValue"->Ot.{wt_type=Bt_bytes;wt_pk=Pk_bytes}|type_name->E.unsupported_wrapper_typetype_name(** [user_defined_type_of_id module_ all_types i] returns the field type name
for the type identied by [i] and which is expected to be in [all_types].
[module_] is the module of the type that this field belong to. If [module_]
is the same as the type [i] module then it won't be added to the field type
name. However if the field type belongs to a different module then it will
be included. This distinction is necessary as OCaml will fail to compile
if the type of a field which is defined within the same module is prefix
with the module name. (This is essentially expecting (rightly) a sub module
with the same name.
*)letuser_defined_type_of_id?(empty_as_unit=true)~(all_types:_list)~file_namei:Ot.field_type=letmodule_prefix=module_prefix_of_file_namefile_nameinmatchTyping_util.type_of_idall_typesiwith|exceptionNot_found->E.programmatic_errorE.No_type_found_for_id|{Tt.file_name;spec;_}ast->ifTyping_util.is_empty_messaget&&empty_as_unitthenOt.Ft_unitelse(letfield_type_module_prefix=module_prefix_of_file_namefile_nameiniffield_type_module_prefix="Wrappers"thenOt.Ft_wrapper_type(wrapper_type_of_type_name(Typing_util.type_name_of_typet))else(letudt_type=matchspecwith|Tt.Enum_->`Enum|Tt.Message_->`Messageinlet{Tt.message_names;_}=Typing_util.type_scope_of_typetinletudt_type_name=type_namemessage_names(Typing_util.type_name_of_typet)iniffield_type_module_prefix=module_prefixthenOt.(Ft_user_defined_type{udt_type;udt_module_prefix=None;udt_type_name})elseOt.(Ft_user_defined_type{udt_type;udt_module_prefix=Somefield_type_module_prefix;udt_type_name;})))letencoding_info_of_field_type~all_typesfield_type:Ot.payload_kind=matchfield_typewith|`Double->Ot.Pk_bits64|`Float->Ot.Pk_bits32|`Int32->Ot.Pk_varintfalse|`Int64->Ot.Pk_varintfalse|`Uint32->Ot.Pk_varintfalse|`Uint64->Ot.Pk_varintfalse|`Sint32->Ot.Pk_varinttrue|`Sint64->Ot.Pk_varinttrue|`Fixed32->Ot.Pk_bits32|`Fixed64->Ot.Pk_bits64|`Sfixed32->Ot.Pk_bits32|`Sfixed64->Ot.Pk_bits64|`Bool->Ot.Pk_varintfalse|`String->Ot.Pk_bytes|`Bytes->Ot.Pk_bytes|`User_definedid->(matchTyping_util.type_of_idall_typesidwith|{Tt.spec=Tt.Enum_;_}->Ot.Pk_varintfalse|{Tt.spec=Tt.Message_;_}->Ot.Pk_bytes)letencoding_of_field~all_types(field:(Pb_field_type.resolved,'a)Tt.field)=letpacked=matchTyping_util.field_optionfield"packed"with|SomePb_option.(Scalar_value(Constant_boolx))->x|Some_->E.invalid_packed_option(Typing_util.field_namefield)|None->falseinletpk=encoding_info_of_field_type~all_types(Typing_util.field_typefield)inpk,Typing_util.field_numberfield,packed,Typing_util.field_defaultfieldletcompile_field_type~unsigned_tag~(all_types:_Tt.proto_typelist)file_optionsfield_optionsfile_namefield_type:Ot.field_type=letocaml_type=matchPb_option.getfield_options"ocaml_type"with|SomePb_option.(Scalar_value(Constant_literal"int_t"))->`Int_t|_->`Noneinletint32_type=matchPb_option.getfile_options"int32_type"with|SomePb_option.(Scalar_value(Pb_option.Constant_literal"int_t"))->Ot.(Ft_basic_typeBt_int)|_->Ot.(Ft_basic_typeBt_int32)inletuint32_type=matchPb_option.getfile_options"int32_type"with|SomePb_option.(Scalar_value(Constant_literal"int_t"))->Ot.(Ft_basic_typeBt_int)|_->Ot.(Ft_basic_typeBt_uint32)inletint64_type=matchPb_option.getfile_options"int64_type"with|SomePb_option.(Scalar_value(Constant_literal"int_t"))->Ot.(Ft_basic_typeBt_int)|_->Ot.(Ft_basic_typeBt_int64)inletuint64_type=matchPb_option.getfile_options"int64_type"with|SomePb_option.(Scalar_value(Constant_literal"int_t"))->Ot.(Ft_basic_typeBt_int)|_->Ot.(Ft_basic_typeBt_uint64)inletmoduleT=structtypesigned_b32=[`Int32|`Sint32|`Fixed32|`Sfixed32]typesigned_b64=[`Int64|`Sint64|`Fixed64|`Sfixed64]typeunsigned_b32=[`Uint32]typeunsigned_b64=[`Uint64]typeint=[signed_b32|unsigned_b32|signed_b64|unsigned_b64]endinmatchfield_type,ocaml_typewith|#T.int,`Int_t->Ot.(Ft_basic_typeBt_int)|#T.signed_b32,_->int32_type|#T.signed_b64,_->int64_type|#T.unsigned_b32,_->ifunsigned_tagthenuint32_typeelseint32_type|#T.unsigned_b64,_->ifunsigned_tagthenuint64_typeelseint64_type|`Double,_->Ot.(Ft_basic_typeBt_float)|`Float,_->Ot.(Ft_basic_typeBt_float)|`Bool,_->Ot.(Ft_basic_typeBt_bool)|`String,_->Ot.(Ft_basic_typeBt_string)|`Bytes,_->Ot.(Ft_basic_typeBt_bytes)|`User_definedid,_->user_defined_type_of_id~all_types~file_nameidletis_mutable?field_namefield_options=matchPb_option.getfield_options"ocaml_mutable"with|SomePb_option.(Scalar_value(Constant_boolv))->v|Some_->Pb_exception.invalid_mutable_option?field_name()|None->falseletocaml_containerfield_options=matchPb_option.getfield_options"ocaml_container"with|None->None|SomePb_option.(Scalar_value(Constant_literalcontainer_name))->Somecontainer_name|Some_->Noneletvariant_of_oneof?include_oneof_name~outer_message_names~unsigned_tag~all_typesfile_optionsfile_nameoneof_field:Ot.variant=letv_constructors=List.map(funfield->letpbtt_field_type=Typing_util.field_typefieldinletfield_type=compile_field_type~unsigned_tag~all_typesfile_options(Typing_util.field_optionsfield)file_namepbtt_field_typeinletvc_payload_kind,vc_encoding_number,_,_=encoding_of_field~all_typesfieldinletvc_constructor=constructor_name(Typing_util.field_namefield)inOt.{vc_constructor;vc_encoding_number;vc_payload_kind;vc_field_type=(matchfield_typewith|Ft_unit->Vct_nullary|_->Vct_non_nullary_constructorfield_type);vc_options=field.field_options;})oneof_field.Tt.oneof_fieldsinletv_name=matchinclude_oneof_namewith|None->type_nameouter_message_names""|Some()->type_nameouter_message_namesoneof_field.Tt.oneof_nameinOt.{v_name;v_constructors}(*
Notes on type level PPX extension handling.
ocaml-protoc supports 2 custom options for defining type level ppx
extensions:
a) message option called ocaml_type_ppx
b) file option called ocaml_all_types_ppx
'ocaml_type_ppx' has priority over 'ocaml_all_types_ppx' extension.
This means that if a message contains 'ocaml_type_ppx' extension then the
associated string will be used for the OCaml generated type ppx extension.
'ocaml_all_types_ppx' is a file option which is a convenient workflow when
the ppx extensions are the same for all types. (Most likely the case).
*)(** utility function to return the string value from a sring option
*)letstring_of_string_optionmessage_name=function|None->None|SomePb_option.(Scalar_value(Constant_strings))->Somes|_->E.invalid_ppx_extension_optionmessage_name(** utility function to implement the priority logic defined in the notes above.
*)letprocess_all_types_ppx_extensionfile_namefile_optionstype_level_ppx_extension=matchtype_level_ppx_extensionwith|Somex->Somex|None->Pb_option.getfile_options"ocaml_all_types_ppx"|>string_of_string_optionfile_nameletcompile_message~(unsigned_tag:bool)(file_options:Pb_option.set)~(all_types:Pb_field_type.resolvedTt.proto_typelist)(file_name:string)(scope:Tt.type_scope)(message:Pb_field_type.resolvedTt.message):Ot.type_list=letmodule_prefix=module_prefix_of_file_namefile_namein(* TODO maybe module_ should be resolved before `compile_message` since
it is common with compile_enum
*)let{Tt.message_name;Tt.message_body;Tt.message_options;_}=messageinlet{Tt.message_names;_}=scopeinlettype_level_ppx_extension=Typing_util.message_optionmessage"ocaml_type_ppx"|>string_of_string_optionmessage_name|>process_all_types_ppx_extensionfile_namefile_optionsin(* In case a message is only made of a `one of` field then we
generate a only a variant rather than both a variant and a message with
a single field. This is an optimization which makes the generated
OCaml code much easier.
*)matchmessage_bodywith|[]->letempty_record=Ot.{er_name=type_namemessage_namesmessage_name}inlettype_=Ot.{module_prefix;spec=Ot.Unitempty_record;type_level_ppx_extension;type_options=message_options;}in[type_]|Tt.Message_oneof_fieldf::[]->letouter_message_names=message_names@[message_name]inletvariant=variant_of_oneof~unsigned_tag~outer_message_names~all_typesfile_optionsfile_namefin[Ot.{module_prefix;spec=Variantvariant;type_level_ppx_extension;type_options=message_options;};]|_->letvariants,fields=List.fold_left(fun(variants,fields)->function|Tt.Message_fieldfield->letpk,encoding_number,packed,_=encoding_of_field~all_typesfieldinletfield_name=Typing_util.field_namefieldinletfield_options=Typing_util.field_optionsfieldinletfield_type=Typing_util.field_typefieldinletocaml_field_type=compile_field_type~unsigned_tag~all_typesfile_optionsfield_optionsfile_namefield_typeinletfield_default=Typing_util.field_defaultfieldinletmutable_=is_mutable~field_namefield_optionsinletrecord_field_type=matchTyping_util.field_labelfieldwith|`Nolabel->(* From proto3 section on default value:
https://goo.gl/NKt9Cc
--
For message fields, the field is not set. Its exact value is
language-dependent. See the generated code guide for details.
--
Since we must support the face that the message won't be sent
we always make such a field an OCaml option. It's the
responsability of the application to check for [None] and
perform any error handling if required. *)letis_message=matchocaml_field_typewith|Ot.Ft_user_defined_type{Ot.udt_type=`Message;_}->true|_->falseinifis_messagethenOt.Rft_optional(ocaml_field_type,encoding_number,pk,None)elseOt.Rft_nolabel(ocaml_field_type,encoding_number,pk)|`Required->Ot.Rft_required(ocaml_field_type,encoding_number,pk,field_default)|`Optional->Ot.Rft_optional(ocaml_field_type,encoding_number,pk,field_default)|`Repeated->letrepeated_type=matchocaml_containerfield_optionswith|None->Ot.Rt_list|Some"repeated_field"->Ot.Rt_repeated_field|Some_->failwith"Invalid ocaml_container attribute value"inOt.Rft_repeated(repeated_type,ocaml_field_type,encoding_number,pk,packed)inletrecord_field=Ot.{rf_label=label_name_of_field_namefield_name;rf_field_type=record_field_type;rf_mutable=mutable_;rf_options=field.field_options;}invariants,record_field::fields(* Message_field *)|Tt.Message_oneof_fieldfield->letouter_message_names=message_names@[message_name]inletvariant=variant_of_oneof~unsigned_tag~include_oneof_name:()~outer_message_names~all_typesfile_optionsfile_namefieldinletrecord_field=Ot.{rf_label=label_name_of_field_namefield.Tt.oneof_name;rf_mutable=false;(* TODO feature:
* Currently the field option of a oneof field is not being parsed
* at all. This enhancement should essentially propage from the
* parser all the way down to here.
*)rf_field_type=Rft_variantvariant;rf_options=field.oneof_options;}inletvariants=lett=Ot.{module_prefix;spec=Variantvariant;type_level_ppx_extension;type_options=Pb_option.empty;}int::variantsinletfields=record_field::fieldsinvariants,fields(* Message_oneof_field *)|Tt.Message_map_fieldmf->let{Tt.map_name;map_number;map_key_type;map_value_type;map_options;}=mfinletkey_type=compile_field_type~unsigned_tag~all_typesfile_optionsmap_optionsfile_namemap_key_typeinletkey_pk=encoding_info_of_field_type~all_typesmap_key_typeinletkey_type=matchkey_typewith|Ot.Ft_basic_typebt->bt|_->failwith"Only Basic Types are supported for map keys"inletvalue_type=compile_field_type~unsigned_tag~all_typesfile_optionsmap_optionsfile_namemap_value_typeinletvalue_pk=encoding_info_of_field_type~all_typesmap_value_typeinletassociative_type=matchocaml_containermap_optionswith|None->Ot.At_list|Some"hashtbl"->Ot.At_hashtable|Some_->failwith"Invalid ocaml_container attribute value for map"inletrecord_field_type=Ot.(Rft_associative(associative_type,map_number,(key_type,key_pk),(value_type,value_pk)))inletrecord_field=Ot.{rf_label=label_name_of_field_namemap_name;rf_field_type=record_field_type;rf_mutable=is_mutable~field_name:map_namemap_options;rf_options=map_options;}invariants,record_field::fields(* Message_map_field *))([],[])message_bodyin(* fold_left body *)letrecord=Ot.{r_name=type_namemessage_namesmessage_name;r_fields=List.revfields;}inlettype_=Ot.{module_prefix;spec=Recordrecord;type_level_ppx_extension;type_options=message_options;}inList.rev(type_::variants)letcompile_enumfile_optionsfile_namescopeenum=let{Tt.enum_name;enum_values;enum_options;_}=enuminletmodule_prefix=module_prefix_of_file_namefile_nameinlet{Tt.message_names;Tt.packages=_}=scopeinletcv_constructors=List.map(fun{Tt.enum_value_name;Tt.enum_value_int;Tt.enum_value_options}->{Ot.cvc_name=constructor_nameenum_value_name;Ot.cvc_binary_value=enum_value_int;Ot.cvc_string_value=enum_value_name;Ot.cvc_options=enum_value_options;})enum_valuesinlettype_level_ppx_extension=Typing_util.enum_optionenum"ocaml_enum_ppx"|>string_of_string_optionenum_name|>process_all_types_ppx_extensionfile_namefile_optionsinOt.{module_prefix;spec=Const_variant{cv_name=type_namemessage_namesenum_name;cv_constructors};type_level_ppx_extension;type_options=enum_options;}letcompile_rpc~(file_name:string)~all_types(rpc:Pb_field_type.resolvedTt.rpc):Ot.rpc=letcompile_ty~stream(ty:int):Ot.rpc_type=letty=user_defined_type_of_id~empty_as_unit:false~all_types~file_nametyinifstreamthenOt.Rpc_streamtyelseOt.Rpc_scalartyin{Ot.rpc_name=rpc.rpc_name;rpc_req=compile_ty~stream:rpc.rpc_req_streamrpc.rpc_req;rpc_res=compile_ty~stream:rpc.rpc_res_streamrpc.rpc_res;}letcompile_service~all_types(service:Pb_field_type.resolvedTt.service):Ot.service={Ot.service_name=service.service_name;service_packages=service.service_packages;service_body=List.map(compile_rpc~file_name:service.service_file_name~all_types)service.service_body;}letcompile1~unsigned_tag~all_types:_->Ot.type_list=function|{Tt.spec=Tt.Messagem;file_name;file_options;scope;_}->compile_message~unsigned_tagfile_options~all_typesfile_namescopem|{Tt.spec=Tt.Enume;file_name;scope;file_options;_}->[compile_enumfile_optionsfile_namescopee]letcompile~unsigned_tag~all_types(proto:Pb_field_type.resolvedTt.proto):Ot.proto=lettys=List.map(funt->List.flatten@@List.map(compile1~unsigned_tag~all_types)t)proto.proto_typesinletservices=List.map(compile_service~all_types)proto.proto_servicesin{Ot.proto_services=services;proto_types=tys}moduleInternal=structletis_mutable=is_mutableletconstructor_name=constructor_nameletmodule_name=module_nameletlabel_name_of_field_name=label_name_of_field_namelettype_name=type_nameletvariant_of_oneof=variant_of_oneofend