123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429(*
* Copyright (c) 2018 Thomas Gazagnaire <thomas@gazagnaire.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)openResultopenUtil.Result.InfixmoduleHeader=structtypet=Shellof[`Sh|`Bash]|OCaml|Otherofstringletppppf=function|Shell `Sh->Fmt.stringppf"sh"|Shell `Bash->Fmt.stringppf"bash"|OCaml->Fmt.stringppf"ocaml"|Others->Fmt.stringppfsletof_string=function|""->None|"sh"->Some(Shell`Sh)|"bash"->Some(Shell`Bash)|"ocaml"->SomeOCaml|s->Some(Others)letinfer_from_filefile=matchFilename.(remove_extension(basenamefile),extensionfile)with|("dune"|"dune-project"),_->Some(Other"scheme")|_,(".ml"|".mli"|".mlt"|".eliom"|".eliomi")->SomeOCaml|_,".sh"->Some(Shell`Sh)|_->Noneendtypesection=int *stringtypecram_value={language:[`Sh|`Bash];non_det:Label.non_detoption}typeocaml_value={env:Ocaml_env.t;non_det:Label.non_detoption;errors:Output.tlist;}typetoplevel_value={env:Ocaml_env.t;non_det:Label.non_detoption}typeinclude_ocaml_file ={part_included:stringoption}typeinclude_other_file ={header:Header.toption}typeinclude_file_kind =|Fk_ocamlofinclude_ocaml_file|Fk_otherofinclude_other_filetypeinclude_value ={file_included:string;file_kind:include_file_kind }typeraw_value={header:Header.toption}typevalue=|Rawofraw_value|OCamlofocaml_value|Cramofcram_value|Topleveloftoplevel_value|Includeofinclude_valuetypet={loc:Location.t;section:sectionoption;dir:stringoption;labels:Label.tlist;legacy_labels:bool;contents:stringlist;skip:bool;version_enabled:bool;set_variables:(string*string)list;unset_variables:stringlist;value:value;}letdump_stringppf s=Fmt.pfppf"%S"sletdump_section=Fmt.(Dump.pairint string)letheadert=matcht.valuewith|Rawb->b.header|OCaml_->SomeHeader.OCaml|Cram{language;_}->Some(Header.Shelllanguage)|Toplevel_->SomeHeader.OCaml|Include{file_kind=Fk_ocaml_;_}->SomeHeader.OCaml|Include{file_kind=Fk_otherb;_}->b.headerletdump_valueppf=function|Raw_->Fmt.stringppf"Raw"|OCaml_->Fmt.stringppf"OCaml"|Cram_->Fmt.stringppf"Cram"|Toplevel_->Fmt.stringppf"Toplevel"|Include_->Fmt.stringppf"Include"letdumpppf({loc;section;labels;contents;value;_}asb)=Fmt.pfppf"{@[loc: %a;@ section: %a;@ labels: %a;@ header: %a;@ contents: %a;@ \
value: %a@]}"Stable_printer.Location.print_loclocFmt.(Dump.optiondump_section)sectionFmt.Dump.(listLabel.pp)labelsFmt.(Dump.optionHeader.pp)(header b)Fmt.(Dump.listdump_string)contentsdump_valuevalueletpp_linessyntax t=letpp=matchsyntaxwith|SomeSyntax.Cram->Fmt.fmt" %s"|SomeSyntax.Mli->funppf->Fmt.fmt"%*s%s"ppf(t.loc.loc_start.pos_cnum+2)""|_->Fmt.stringinFmt.(list~sep:(any"\n")pp)letlstripstring=lethpad=Misc.hpad_of_lines[string]inAstring.String.with_index_rangestring~first:hpadletpp_contents ?syntaxppft=match(syntax,t.contents)with|SomeSyntax.Mli,[line]->Fmt.pfppf"%s"line|SomeSyntax.Mli,lines->Fmt.pfppf "@\n%a@\n"(pp_linessyntaxt)(List.maplstriplines)|(SomeCram|SomeNormal|None),[]->()|(SomeCram|SomeNormal|None),_->Fmt.pfppf"%a\n"(pp_linessyntaxt)t.contentsletpp_errorsppft=matcht.valuewith|OCaml{errors;_}whenList.lengtherrors>0->Fmt.string ppf"```mdx-error\n";Fmt.pfppf"%a"Fmt.(list~sep:nopOutput.pp)errors;Fmt.stringppf"```\n"|_->()letpp_footer?syntaxppf_=matchsyntaxwith|SomeSyntax.Mli->()|SomeSyntax.Cram->()|_->Fmt.stringppf"```\n"letpp_legacy_labelsppf=function|[]->()|l->Fmt.pfppf" %a"Fmt.(list~sep:(any",")Label.pp)lletpp_labelsppf=function|[]->()|l->Fmt.pfppf"<!-- $MDX %a -->\n"Fmt.(list~sep:(any",")Label.pp)lletpp_header?syntaxppft=matchsyntaxwith|SomeSyntax.Cram->(matcht.labelswith|[]->()|[Non_detNone]->Fmt.pfppf"<-- non-deterministic\n"|[Non_det(SomeNd_output)]->Fmt.pfppf"<-- non-deterministic output\n"|[Non_det(SomeNd_command)]->Fmt.pfppf"<-- non-deterministic command\n"|_->failwith"cannot happen: checked during parsing")|SomeSyntax.Mli->()|_->ift.legacy_labelsthenFmt.pfppf"```%a%a\n"Fmt.(optionHeader.pp)(headert)pp_legacy_labelst.labelselseFmt.pfppf"%a```%a\n"pp_labelst.labelsFmt.(optionHeader.pp)(headert)letpp?syntaxppfb=pp_header?syntax ppfb;pp_contents?syntaxppfb;pp_footer?syntax ppfb;pp_errorsppfbletdirectoryt=t.dirletfilet=matcht.valuewithIncludet->Somet.file_included|_->Noneletnon_dett=matcht.valuewith|OCamlb->b.non_det|Cramb->b.non_det|Toplevelb->b.non_det|Include_|Raw_->Noneletskipt=t.skipletset_variables t=t.set_variablesletunset_variablest=t.unset_variablesletvaluet=t.valueletsectiont=t.sectionletguess_ocaml_kindcontents=letrecaux=function|[]->`Code|h::t->leth=String.trimhinifh=""thenauxtelseifString.lengthh>1&&h.[0]='#'then`Toplevelelse`Codeinauxcontentsletends_by_semi_semic=matchList.revcwith|h::_->letlen=String.lengthhinlen>2&&h.[len-1]=';'&&h.[len-2]=';'|_->falseletpp_line_directiveppf(file,line)=Fmt.pfppf"#%d %S"linefileletline_directive=Fmt.to_to_stringpp_line_directiveletexecutable_contents~syntaxb=letcontents=matchb.valuewith|OCaml_->b.contents|Raw_|Cram_|Include_->[]|Toplevel_->letphrases=Toplevel.of_lines~syntax~loc:b.locb.contents inList.flatten(List.map(fun(t:Toplevel.t)->matcht.commandwith|[]->[]|cs->letmks=String.make(t.hpad+2)' '^sinline_directive(t.pos.pos_fname,t.pos.pos_lnum)::List.mapmkcs)phrases)inifcontents=[]||ends_by_semi_semicontentsthencontentselsecontents@[";;"]letversion_enabled version=letopenUtil.Result.InfixinOcaml_version.of_stringSys.ocaml_version>>|funcurr_version->matchversionwith|Some(op,v)->Label.Relation.compareop(Ocaml_version.comparecurr_versionv)0|None->trueletget_labelf(labels:Label.tlist)=Util.List.find_mapflabelsletlabel_not_allowed ~label~kind=Util.Result.errorf "`%s` label is not allowed for %s blocks."labelkindletlabel_required~label~kind=Util.Result.errorf"`%s` label is required for %s blocks."labelkindletcheck_not_setmsg=function|Some_->Util.Result.errorfmsg|None->Ok()letcheck_no_errors=function|[]->Ok()|_::_->Util.Result.errorf"error block cannot be attached to a non-OCaml block"typeblock_config={non_det:Label.non_detoption;part:stringoption;env:stringoption;dir:stringoption;skip:bool;version:(Label.Relation.t*Ocaml_version.t)option;set_variables:(string*string)list;unset_variables:stringlist;file_inc:stringoption;}letget_block_config l={non_det=get_label(function|Non_det(Somex)->Somex|Non_detNone->SomeLabel.default_non_det|_->None)l;part=get_label(functionPartx->Somex|_->None)l;env=get_label(functionEnvx->Somex|_->None)l;dir=get_label(functionDirx->Somex|_->None)l;skip=List.exists(functionLabel.Skip->true|_->false)l;version=get_label(functionVersion(x,y)->Some(x,y)|_->None)l;set_variables=List.filter_map(functionLabel.Set(v,x)->Some(v,x)|_->None)l;unset_variables=List.filter_map(functionLabel.Unsetx->Somex|_->None)l;file_inc=get_label(functionFilex->Somex|_->None)l;}letmk_ocaml~config~contents~errors=letkind="OCaml" inmatch configwith|{file_inc=None;part=None;env;non_det;_}->(match guess_ocaml_kindcontentswith|`Code->Ok(OCaml{env=Ocaml_env.mkenv;non_det;errors})|`Toplevel->Util.Result.errorf"toplevel syntax is not allowed in OCaml blocks.")|{file_inc=Some_;_}->label_not_allowed~label:"file"~kind|{part=Some_;_}->label_not_allowed~label:"part"~kindletmk_cram?language~config~header~errors()=letkind="shell"inmatch configwith|{file_inc=None;part=None;env=None;non_det;_}->check_no_errors errors>>|fun()->let language=Util.Option.valuelanguage~default:(matchheaderwith|Some(Header.Shelllanguage)->language|_->`Sh)inCram{language;non_det}|{file_inc =Some_;_}->label_not_allowed~label:"file"~kind|{part=Some_;_}->label_not_allowed~label:"part"~kind|{env=Some_;_}->label_not_allowed~label:"env"~kindletmk_toplevel~config~contents~errors=letkind="toplevel" inmatchconfigwith|{file_inc=None;part=None;env;non_det;_}->(match guess_ocaml_kindcontentswith|`Code->Util.Result.errorf"invalid toplevel syntax in toplevel blocks."|`Toplevel->check_no_errorserrors>>|fun()->Toplevel{env=Ocaml_env.mkenv;non_det})|{file_inc=Some_;_}->label_not_allowed~label:"file"~kind|{part=Some_;_}->label_not_allowed~label:"part"~kindletmk_include~config~header~errors =letkind="include" inmatch configwith|{file_inc=Somefile_included;part;non_det=None;env =None;_}->(check_no_errorserrors>>=fun()->match headerwith|SomeHeader.OCaml->letfile_kind=Fk_ocaml{part_included=part}inOk(Include{file_included;file_kind})|_->(matchpartwith|None ->letfile_kind=Fk_other{header}inOk(Include{file_included;file_kind})|Some _->label_not_allowed~label:"part"~kind:"non-OCaml include"))|{file_inc=None;_}->label_required~label:"file"~kind|{non_det=Some _;_}->label_not_allowed~label:"non-deterministic"~kind|{env=Some_;_}->label_not_allowed~label:"env"~kindletinfer_block~config~header~contents ~errors=matchconfigwith|{file_inc=Some_;_}->mk_include~config~header~errors|{file_inc =None;part;_}->(match headerwith|Some(Header.Shelllanguage)->mk_cram ~language~config~header~errors()|SomeHeader.OCaml->(matchguess_ocaml_kindcontentswith|`Code->mk_ocaml~config~contents~errors|`Toplevel ->mk_toplevel~config~contents~errors)|_->check_not_set"`part` label requires a `file` label."part>>=fun()->check_no_errorserrors>>|fun()->Raw{header })letmk~loc~section ~labels~legacy_labels~header~contents~errors=letblock_kind=get_label(functionBlock_kindx->Somex|_->None)labelsinletconfig=get_block_configlabelsin(matchblock_kindwith|SomeOCaml ->mk_ocaml~config~contents~errors|SomeCram ->mk_cram ~config~header~errors()|Some Toplevel->mk_toplevel~config~contents~errors|SomeInclude -> mk_include~config~header~errors|None-> infer_block~config~header~contents~errors)>>=funvalue->version_enabledconfig.version>>|funversion_enabled ->{loc;section;dir=config.dir;labels;legacy_labels;contents;skip=config.skip;version_enabled;set_variables=config.set_variables;unset_variables=config.unset_variables;value;}letmk_include~loc~section~labels=matchget_label(functionFilex->Somex|_->None)labelswith|Somefile_inc->letheader=Header.infer_from_filefile_incinmk~loc~section~labels~legacy_labels:false~header~contents:[]~errors:[]|None->label_required~label:"file"~kind:"include"letis_active?section:st=let active=matchswith|Somep->(matcht.sectionwith|Somes->Re.execp(Re.Perl.compile_patp)(snds)|None->Re.execp(Re.Perl.compile_patp)"")|None->trueinactive&&t.version_enabled&¬t.skip