123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628(* Generates Hardcaml interfaces for variant types where each case wraps an inner
interface type. For example:
{[
type 'a t =
| Foo of 'a Foo.t
| Bar of 'a Bar.t
[@@deriving hardcaml_variants]
]}
The Make functor validates that all interface operations (map2, etc.) are performed on
values matching the configured kind. *)openBaseopenPpxlibopenPpxlib.Ast_builder.Defaultletderiver="hardcaml_variants"letraise_errorf=Location.raise_errorfletis_ptyp_var_with_nametypname=matchPpxlib_jane.Shim.Core_type_desc.of_parsetreetyp.ptyp_descwith|Ptyp_var(v,_)whenString.equalvname->true|_->false;;moduleCase=structtypet={name:string;loc:location;mname:Longident.t}(* [Foo] -> "foo" *)letlabel_namec=String.uncapitalizec.name(* [Foo] -> "foo_exn" *)letextractor_fn_namec=label_namec^"_exn"(* [Foo] -> "Foo" *)letreexport_namec=c.name(* [Foo] -> ['a Foo.t]; for use in function signatures *)letpayload_type~locc=ptyp_constr~loc(Located.mk~loc(Ldot(c.mname,"t")))[ptyp_var~loc"a"];;end(* Construct the [Kind.t] declaration. In the example at the top of this file, this would
look like [type t = Foo | Bar [@@deriving sexp]]. *)letkind_variant_type_decl~loccases=letconstrs=List.mapcases~f:(func->constructor_declaration~loc:c.Case.loc~name:(Located.mk~loc:c.Case.locc.Case.name)~args:(Pcstr_tuple[])~res:None)in{(type_declaration~loc~name:(Located.mk~loc"t")~params:[]~cstrs:[]~kind:(Ptype_variantconstrs)~private_:Public~manifest:None)withptype_attributes=[{attr_name=Located.mk~loc"deriving";attr_payload=PStr[[%strisexp]];attr_loc=loc}]};;(* Module signature for re-exporting the inner interface. For example:
[ | Foo of 'a Foo.t ] becomes [module Foo : Interface.S with type 'a t = 'a Foo.t]. *)letcase_interface_module_sig~locc=letinterface_s=Located.mk~loc(Ldot(Ldot(Lident"Ppx_hardcaml_runtime","Interface"),"S"))inpsig_module~loc(module_declaration~loc~name:(Located.mk~loc(Some(Case.reexport_namec)))~type_:(pmty_with~loc(pmty_ident~locinterface_s)[Pwith_type(Located.mk~loc(Lident"t"),type_declaration~loc~name:(Located.mk~loc"t")~params:[ptyp_var~loc"a",(NoVariance,NoInjectivity)]~cstrs:[]~kind:Ptype_abstract~private_:Public~manifest:(Some(Case.payload_type~locc)))]));;(* Signature for an _exn extractor, i.e. [val foo_exn : 'a t -> 'a Foo.t]. *)letcase_exn_val_sig~locc=psig_value~loc(value_description~loc~name:(Located.mk~loc(Case.extractor_fn_namec))~type_:[%type:'at->[%tCase.payload_type~locc]]~prim:[]);;letextract_case~varcd=matchcd.pcd_argswith|Pcstr_tuple[tuple_arg]->letcore_type=Ppxlib_jane.Shim.Pcstr_tuple_arg.to_core_typetuple_argin(matchPpxlib_jane.Shim.Core_type_desc.of_parsetreecore_type.ptyp_descwith|Ptyp_constr({txt=Ldot(mname,_);_},[v])whenis_ptyp_var_with_namevvar->Some{Case.name=cd.pcd_name.txt;loc=cd.pcd_loc;mname}|_->None)|_->None;;letvalidate_constructors~loc~varcds=letcases=List.mapcds~f:(funcd->matchextract_case~varcdwith|Somec->c|None->raise_errorf~loc:cd.pcd_loc"[%s] each variant case must have exactly one argument of type 'a Module.t"deriver)inifList.is_emptycasesthenraise_errorf~loc"[%s] variant must have at least one case"deriver;cases;;(* Helpers to build constructor patterns/expressions *)letconstr_pat~locnamearg=ppat_construct~loc(Located.mk~loc(Lidentname))argletconstr_exp~locnamearg=pexp_construct~loc(Located.mk~loc(Lidentname))argletkind_pat~locname=ppat_construct~loc(Located.mk~loc(Ldot(Lident"Kind",name)))None;;(* Implements [_exn] extractors *)letexpand_exn_fn~locc=letpat=constr_pat~locc.Case.name(Some[%pat?x])inletmsg=pexp_constant~loc(Pconst_string("expected "^c.Case.name^", got",loc,None))invalue_binding~loc~pat:(pvar~loc(Case.extractor_fn_namec))~expr:[%exprfunt->matchtwith|[%ppat]->x|_->raise_s[%message[%emsg](t:_t)]];;(* Declares
[val map_variants : 'a t -> foo:('a Foo.t -> 'b Foo.t) -> bar:('a Bar.t -> 'b Bar.t) -> ... -> 'b t] *)letmap_variants_val_sig~loccases=letresult_type=ptyp_constr~loc(Located.mk~loc(Lident"t"))[ptyp_var~loc"b"]inletinput_type=ptyp_constr~loc(Located.mk~loc(Lident"t"))[ptyp_var~loc"a"]inletcase_arg_typec=letinner_a=Case.payload_type~loccinletinner_b=ptyp_constr~loc(Located.mk~loc(Ldot(c.Case.mname,"t")))[ptyp_var~loc"b"]inptyp_arrow~locNolabelinner_ainner_binletfn_type=List.fold_rightcases~init:result_type~f:(funcacc->ptyp_arrow~loc(Labelled(Case.label_namec))(case_arg_typec)acc)inletfull_type=ptyp_arrow~locNolabelinput_typefn_typeinpsig_value~loc(value_description~loc~name:(Located.mk~loc"map_variants")~type_:full_type~prim:[]);;(* Implements [map_variants] *)letexpand_map_variants~loccases=letmatch_cases=List.mapcases~f:(func->letlabel=Case.label_namecinletpat=constr_pat~locc.Case.name(Some[%pat?x])inletfn_call=pexp_apply~loc(evar~loclabel)[Nolabel,[%exprx]]inletresult=constr_exp~locc.Case.name(Somefn_call)incase~lhs:pat~guard:None~rhs:result)inletmatch_expr=pexp_match~loc[%exprt]match_casesinletbody_with_labels=List.fold_rightcases~init:match_expr~f:(funcacc->pexp_fun~loc(Labelled(Case.label_namec))None(pvar~loc(Case.label_namec))acc)inletfull_expr=pexp_fun~locNolabelNone[%pat?t]body_with_labelsinpstr_value~locNonrecursive[value_binding~loc~pat:(pvar~loc"map_variants")~expr:full_expr];;(* Implements Kind module *)letexpand_kind_module~loccases=pstr_module~loc(module_binding~loc~name:(Located.mk~loc(Some"Kind"))~expr:(pmod_structure~loc[pstr_type~locRecursive[kind_variant_type_decl~loccases]]));;(* Re-exports a module from a case, i.e. [ | Foo of 'a Foo.t ] becomes [module Foo = Foo]. *)letexpand_module_reexport~locc=pstr_module~loc(module_binding~loc~name:(Located.mk~loc(Some(Case.reexport_namec)))~expr:(pmod_ident~loc(Located.mk~locc.Case.mname)));;(* Defines the [module type S] outputted by [Make] *)letexpand_module_type_s~loccases=letmodule_sigs=List.mapcases~f:(case_interface_module_sig~loc)inletexn_sigs=List.mapcases~f:(case_exn_val_sig~loc)inpstr_modtype~loc(module_type_declaration~loc~name:(Located.mk~loc"S")~type_:(Some(pmty_signature~loc([[%sigi:valkind:Kind.t];[%sigi:includePpx_hardcaml_runtime.Interface.Swithtype'at='at];[%sigi:valvalidate:'at->unit]]@module_sigs@exn_sigs@[map_variants_val_sig~loccases]))));;(* Generate the [validate] function which checks whether the passed in argument matches
the kind specified in [Make] *)letexpand_validate~loccases=letmatch_cases=List.mapcases~f:(func->letkpat=kind_pat~locc.Case.nameinlettpat=constr_pat~locc.Case.name(Some[%pat?_])incase~lhs:(ppat_tuple~loc[kpat;tpat])~guard:None~rhs:[%expr()])inletwildcard=case~lhs:[%pat?_,_]~guard:None~rhs:[%exprraise_s[%message"mismatched tag"(kind:Kind.t)(t:_t)]]in[%exprfunt->[%epexp_match~loc[%exprkind,t](match_cases@[wildcard])]];;(* map2: apply f to matching constructors *)letexpand_map2~loccases=letmatch_cases=List.mapcases~f:(func->letapat=constr_pat~locc.Case.name(Some[%pat?a])inletbpat=constr_pat~locc.Case.name(Some[%pat?b])inletmap2id=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"map2")))inletcall=pexp_apply~locmap2id[Labelled"f",[%exprf];Nolabel,[%expra];Nolabel,[%exprb]]inletresult=constr_exp~locc.Case.name(Somecall)incase~lhs:(ppat_tuple~loc[apat;bpat])~guard:None~rhs:result)inletwildcard=case~lhs:[%pat?_,_]~guard:None~rhs:[%exprraise_s[%message"map2 expects matching tags"(a:_t)(b:_t)]]in[%exprfunab~(f@local)->validatea;validateb;[%epexp_match~loc[%expra,b](match_cases@[wildcard])]];;(* iter2: apply f to matching constructors *)letexpand_iter2~loccases=letmatch_cases=List.mapcases~f:(func->letapat=constr_pat~locc.Case.name(Some[%pat?a])inletbpat=constr_pat~locc.Case.name(Some[%pat?b])inletiter2id=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"iter2")))inletcall=pexp_apply~lociter2id[Labelled"f",[%exprf];Nolabel,[%expra];Nolabel,[%exprb]]incase~lhs:(ppat_tuple~loc[apat;bpat])~guard:None~rhs:call)inletwildcard=case~lhs:[%pat?_,_]~guard:None~rhs:[%exprraise_s[%message"iter2 expects matching tags"(a:_t)(b:_t)]]in[%exprfunab~(f@local)->validatea;validateb;[%epexp_match~loc[%expra,b](match_cases@[wildcard])]];;(* map: apply f to a single value *)letexpand_map~loccases=letmatch_cases=List.mapcases~f:(func->letpat=constr_pat~locc.Case.name(Some[%pat?x])inletmapid=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"map")))inletcall=pexp_apply~locmapid[Labelled"f",[%exprf];Nolabel,[%exprx]]inletresult=constr_exp~locc.Case.name(Somecall)incase~lhs:pat~guard:None~rhs:result)in[%exprfuna~(f@local)->validatea;[%epexp_match~loc[%expra]match_cases]];;(* iter: apply f to a single value *)letexpand_iter~loccases=letmatch_cases=List.mapcases~f:(func->letpat=constr_pat~locc.Case.name(Some[%pat?x])inletiterid=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"iter")))inletcall=pexp_apply~lociterid[Labelled"f",[%exprf];Nolabel,[%exprx]]incase~lhs:pat~guard:None~rhs:call)in[%exprfuna~(f@local)->validatea;[%epexp_match~loc[%expra]match_cases]];;(* to_list: pass through to inner module *)letexpand_to_list~loccases=letmatch_cases=List.mapcases~f:(func->letpat=constr_pat~locc.Case.name(Some[%pat?x])inletto_list_id=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"to_list")))incase~lhs:pat~guard:None~rhs:(pexp_apply~locto_list_id[Nolabel,[%exprx]]))in[%exprfunt->validatet;[%epexp_match~loc[%exprt]match_cases]];;(* port_names_and_widths: pass through to inner module *)letexpand_port_names_and_widths~loccases=letmatch_cases=List.mapcases~f:(func->letkpat=kind_pat~locc.Case.nameinletpnw=pexp_ident~loc(Located.mk~loc(Ldot(c.Case.mname,"port_names_and_widths")))inletresult=constr_exp~locc.Case.name(Somepnw)incase~lhs:kpat~guard:None~rhs:result)inpexp_match~loc[%exprkind]match_cases;;(* Make functor - always re-exports all inner modules since Make is a fresh structure *)letexpand_make_functor~loccases=letmodule_reexports=List.mapcases~f:(expand_module_reexport~loc)inlett_module=pstr_module~loc(module_binding~loc~name:(Located.mk~loc(Some"T"))~expr:(pmod_structure~loc[[%stritypenonrec'at='at[@@derivingequal~localize,compare~localize,sexp_of]];pstr_value~locNonrecursive[value_binding~loc~pat:(pvar~loc"validate")~expr:(expand_validate~loccases)];pstr_value~locNonrecursive[value_binding~loc~pat:(pvar~loc"port_names_and_widths")~expr:(expand_port_names_and_widths~loccases);value_binding~loc~pat:(pvar~loc"map2")~expr:(expand_map2~loccases);value_binding~loc~pat:(pvar~loc"iter2")~expr:(expand_iter2~loccases);value_binding~loc~pat:(pvar~loc"map")~expr:(expand_map~loccases);value_binding~loc~pat:(pvar~loc"iter")~expr:(expand_iter~loccases);value_binding~loc~pat:(pvar~loc"to_list")~expr:(expand_to_list~loccases)];[%striincludePpx_hardcaml_runtime.Interface.Make(structtypenonrec'at='atletequal=equalletequal__local=equal__localletcompare=compareletcompare__local=compare__localletsexp_of_t=sexp_of_tletport_names_and_widths=port_names_and_widthsletiter=iterletiter2=iter2letmap=mapletmap2=map2letto_list=to_listend)]]))inletexn_rebinds=List.mapcases~f:(func->letname=Case.extractor_fn_namecin[%strilet[%ppvar~locname]=[%epexp_ident~loc(Located.mk~loc(Lidentname))]])inletmap_variants_rebind=[%striletmap_variants=map_variants]inpstr_module~loc(module_binding~loc~name:(Located.mk~loc(Some"Make"))~expr:(pmod_functor~loc(Ppxlib_jane.Shim.Functor_parameter.to_parsetree(Named(Located.mk~loc(Some"Config"),pmty_signature~loc[[%sigi:valkind:Kind.t]],[])))(pmod_structure~loc([[%striincludeConfig]]@module_reexports@[t_module;[%striincludeT]]@exn_rebinds@[map_variants_rebind]))));;(* Generate structure items for a variant type *)letstr_of_type~loc~varcds=letcases=validate_constructors~loc~varcdsin[pstr_value~locNonrecursive(List.mapcases~f:(expand_exn_fn~loc));expand_map_variants~loccases;expand_kind_module~loccases;expand_module_type_s~loccases;expand_make_functor~loccases];;letsig_kind_module~loccases=psig_module~loc(module_declaration~loc~name:(Located.mk~loc(Some"Kind"))~type_:(pmty_signature~loc[psig_type~locRecursive[kind_variant_type_decl~loccases]]));;letsig_module_type_s~loccases=letmodule_sigs=List.mapcases~f:(case_interface_module_sig~loc)inletexn_sigs=List.mapcases~f:(case_exn_val_sig~loc)inpsig_modtype~loc(module_type_declaration~loc~name:(Located.mk~loc"S")~type_:(Some(pmty_signature~loc([[%sigi:valkind:Kind.t];[%sigi:includePpx_hardcaml_runtime.Interface.Swithtype'at='at];[%sigi:valvalidate:'at->unit]]@module_sigs@exn_sigs@[map_variants_val_sig~loccases]))));;letsig_make_functor~loc=psig_module~loc(module_declaration~loc~name:(Located.mk~loc(Some"Make"))~type_:(pmty_functor~loc(Ppxlib_jane.Shim.Functor_parameter.to_parsetree(Named(Located.mk~locNone,pmty_signature~loc[[%sigi:valkind:Kind.t]],[])))(pmty_ident~loc(Located.mk~loc(Lident"S")))));;(* Generate signature items for a variant type *)letsig_of_type~loc~varcds=letcases=validate_constructors~loc~varcdsinList.mapcases~f:(case_exn_val_sig~loc)@[map_variants_val_sig~loccases;sig_kind_module~loccases;sig_module_type_s~loccases;sig_make_functor~loc];;letstr_of_type_decltype_decl=letonly_param=matchtype_decl.ptype_paramswith|[(param,_)]->Someparam.ptyp_desc|_->Noneinmatch(type_decl.ptype_kind,Option.maponly_param~f:Ppxlib_jane.Shim.Core_type_desc.of_parsetree)with|Ptype_variantconstructors,Some(Ptyp_var(var,_))->str_of_type~loc:type_decl.ptype_loc~varconstructors|_->raise_errorf~loc:type_decl.ptype_loc"[%s] only supports variant types"deriver;;letsig_of_type_decltype_decl=match(type_decl.ptype_kind,Option.map(List.hdtype_decl.ptype_params)~f:(fun(p,_)->Ppxlib_jane.Shim.Core_type_desc.of_parsetreep.ptyp_desc))with|Ptype_variantcds,Some(Ptyp_var(var,_))->sig_of_type~loc:type_decl.ptype_loc~varcds|_->raise_errorf~loc:type_decl.ptype_loc"[%s] only supports variant types"deriver;;letregister()=lethardcaml_variants_internal=Deriving.add"hardcaml_variants_internal"~str_type_decl:(Deriving.Generator.makeDeriving.Args.empty(fun~loc:_~path:_(_,type_declarations)->List.concat_maptype_declarations~f:str_of_type_decl))~sig_type_decl:(Deriving.Generator.makeDeriving.Args.empty(fun~loc:_~path:_(_,type_declarations)->List.concat_maptype_declarations~f:sig_of_type_decl))in(* Ordering of the derivers of the alias below matters. Empirically, the derivers are
expanded in reverse order of the list. *)Deriving.add_aliasderiver[hardcaml_variants_internal;Ppx_sexp_conv.sexp_of;Ppx_compare.equal_local;Ppx_compare.compare_local]|>Deriving.ignore;;