123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477openFormatexceptionInclude_loop_detectedofstringexceptionOf_sexp_error=Pre_sexp.Of_sexp_errorexceptionMacro_conv_errorofexn*Sexp.t*[`expandedofSexp.t]let()=letopenSexpinConv.Exn_converter.add~finalise:false[%extension_constructorMacro_conv_error](function|Macro_conv_error(exn,unexpanded,`expandedexpanded)->List[Atom"Sexplib.Macro.Macro_conv_error";List[Conv.sexp_of_exnexn;unexpanded;List[Atom"expanded";expanded]]]|_->assertfalse)letmacro_errorerrt=Of_sexp_error(Failure(sprintf"Error evaluating macros: %s"err),t)type'aconv=[`Resultof'a|`Errorofexn*Sexp.t]type'aannot_conv=(* 'a Sexp.Annotated.conv = *)[`Resultof'a|`Errorofexn*Sexp.Annotated.t]letsexp_of_convsexp_of_a=function|`Resulta->Sexp.List[Atom"Result";a|>sexp_of_a]|`Error(exn,sexp)->List[Atom"Error";List[Sexplib0.Sexp_conv.sexp_of_exnexn;sexp]]letsexp_of_annot_convsexp_of_a=function|`Resulta->Sexp.List[Atom"Result";a|>sexp_of_a]|`Error(exn,annotated_sexp)->List[Atom"Error";List[Sexplib0.Sexp_conv.sexp_of_exnexn;annotated_sexp|>Sexp.Annotated.get_sexp]]moduleList=struct(* Think about tail recursion when adding more list functions in here. *)letlength=List.lengthletfold_left=List.fold_leftletmem=List.memletassq=List.assqletiterx~f=List.iterfxletrev_append=List.rev_appendletrev=List.revletassoc=List.assocletmapl~f=letrecauxacc=function|[]->List.revacc|hd::tl->aux((fhd)::acc)tlinaux[]lletconcat_mapl~f=letrecauxacc=function|[]->List.revacc|hd::tl->aux(List.rev_append(fhd)acc)tlinaux[]lletrecfind_map~fxs=matchxswith|[]->None|x::xs->matchfxwith|Somex->Somex|None->find_map~fxsletexists~fxs=List.existsfxsletrecfind_a_dup=function|[]->None|x::xs->ifList.memxxsthenSomexelsefind_a_dupxsendlet(@)=`redefine_a_tail_rec_append_if_you_need_itlet_=(@)moduleVars=structincludeSet.Make(String)letadd_listsetxs=List.fold_left(funvarsv->addvvars)setxsletof_listxs=add_listemptyxsend(* Map from template names to template argument lists and bodies. The argument
lists are not necessary for the formal evaluation rules, but are useful to
catch errors early. *)moduleBindings=Map.Make(String)(* A physical association list mapping sexps after :include are inlined to sexps
that they originate from. This map allows us to recover the original sexp
that gave rise to an error and to give a precise error location. *)typetrail=(Sexp.t*Sexp.t)listletrecfind_argresulttrail=tryfind_arg(List.assqresulttrail)trailwithNot_found->resultletatom=function|Sexp.Atomstr->str|Sexp.List_ast->raise(macro_error"Atom expected"t)letatoms=function|Sexp.Atom_ast->raise(macro_error"Atom list expected"t)|Sexp.Listts->List.map~f:atomts(* If [~raise_if_any:true], raise an error if a free variable is encountered. *)letfree_variables_gen~raise_if_anyts=(* Tail-recursive w.r.t the number of sexps in a list, but not sexp depth. *)letrecfree_in_listboundtsacc=matchtswith|Sexp.List(Sexp.Atom":let"::v::vs::def)::ts->letacc=free_in_list(Vars.add_listbound(atomsvs))defaccinfree_in_list(Vars.add(atomv)bound)tsacc|t::ts->letacc=freeboundtaccinfree_in_listboundtsacc|[]->accandfreeboundtacc=matchtwith|Sexp.List(Sexp.Atom":use"::v::args)->letacc=ifVars.mem(atomv)boundthenaccelseifraise_if_anythenletmsg="Undefined variable (included files cannot reference variables from outside)"inraise(macro_errormsgv)elseVars.add(atomv)accinList.fold_left(funacct->freeboundtacc)accargs|Sexp.Listts->free_in_listboundtsacc|Sexp.Atom_->accinfree_in_listVars.emptytsVars.emptyletcheck_no_free_variablests=ignore(free_variables_gen~raise_if_any:truets)letfree_variablests=free_variables_gen~raise_if_any:falsetsletexpand_local_macros_exn~trailts=letadd_result=matchtrailwith|None->fun~arg:_~result:_->()|Someref->fun~arg~result->ref:=(result,arg)::!refin(* tail-recursive *)letrecexpand_listdefstsacc=matchtswith|Sexp.List(Sexp.Atom":let"::v::args::def)ast::ts->ifdef=[]thenraise(macro_error"Empty let bodies not allowed"t);letv=atomvinletargs=atomsargsinletfree=free_variablesdefinletargs_set=Vars.of_listargsinletunused=Vars.diffargs_setfreeinifnot(Vars.is_emptyunused)thenraise(macro_error(sprintf"Unused variables: %s"(String.concat", "(Vars.elementsunused)))t);letundeclared=Vars.difffreeargs_setinifnot(Vars.is_emptyundeclared)thenraise(macro_error(sprintf"Undeclared arguments in let: %s"(String.concat", "(Vars.elementsundeclared)))t);beginmatchList.find_a_dupargswith|None->()|Somedup->raise(macro_error(sprintf"Duplicated let argument: %s"dup)t)end;expand_list(Bindings.addv(args,def)defs)tsacc|t::ts->expand_listdefsts(List.rev_append(expanddefst)acc)|[]->List.revaccandexpanddefst=matchtwith|Sexp.Atom(":use"|":let"|":include"|":concat"ass)->raise(macro_error("Unexpected "^s)t)|Sexp.Atom_ast->[t]|Sexp.List(Sexp.Atom":use"::v::args)->letsplit_arg=function|Sexp.List(Sexp.Atomv::def)->v,def|arg->raise(macro_error"Malformed argument"arg)inletevaluate_and_bindarg_defs(v,def)=(* It is important we evaluate with respect to defs here, to avoid one
argument shadowing the next one. *)letdef=expand_listdefsdef[]inBindings.addv([],def)arg_defsinletformal_args,body=tryBindings.find(atomv)defswithNot_found->raise(macro_error"Undefined variable"v)inletargs=List.map~f:split_argargsinletarg_names=List.map~f:(fun(v,_)->v)argsinifarg_names<>formal_argsthenraise(macro_error(sprintf("Formal args of %s differ from supplied args,"^^" formal args are [%s]")(atomv)(String.concat", "formal_args))t);letdefs=List.fold_leftevaluate_and_bindBindings.emptyargsinexpand_listdefsbody[]|Sexp.List(Sexp.Atom":concat"::ts)ast->letts=expand_listdefsts[]inletts=tryList.map~f:atomtswith_->leterror=letappl=Sexp.List(Sexp.Atom":concat"::ts)insprintf"Malformed concat application: %s"(Sexp.to_stringappl)inraise(macro_errorerrort)inletresult=Sexp.Atom(String.concat""ts)inadd_result~arg:t~result;[result]|Sexp.Listts->letts=expand_listdefsts[]inletresult=Sexp.Listtsinadd_result~arg:t~result;[result]inexpand_listBindings.emptyts[]letexpand_local_macrosts=try`Result(expand_local_macros_exnts~trail:None)withOf_sexp_error(e,t)->`Error(e,t)moduletypeSexp_loader=sigmoduleMonad:sigtype'atvalreturn:'a->'atmoduleMonad_infix:sigval(>>=):'at->('a->'bt)->'btendmoduleList:sigvaliter:'alist->f:('a->unitt)->unittvalmap:'alist->f:('a->'bt)->'blisttendendvalload_sexps:string->Sexp.tlistMonad.tvalload_annotated_sexps:string->Sexp.Annotated.tlistMonad.tendmoduleLoader(S:Sexp_loader)=structmoduleM=S.MonadopenM.Monad_infixtype'afile_contents=(string*'a)listtypemode=[`FastofSexp.tlistfile_contents|`Find_errorofSexp.Annotated.tlistfile_contents]letmake_absolute_path~with_respect_tofile=ifFilename.is_relativefilethenFilename.concat(Filename.dirnamewith_respect_to)fileelsefileletload_all_includesfile:Sexp.tlistfile_contentsM.t=letfile_contents=ref[]inletrecloadvisitedfile=ifList.memfilevisitedthenraise(Include_loop_detectedfile);ifList.memfile(List.map~f:fst!file_contents)thenM.return()elsebeginS.load_sexpsfile>>=funts->file_contents:=(file,ts)::!file_contents;M.List.iterts~f:(load_includes(file::visited)file)endandload_includesvisitedfile=function|Sexp.List[Sexp.Atom":include";Sexp.Atominclude_file]->letinclude_file=make_absolute_path~with_respect_to:fileinclude_fileinloadvisitedinclude_file|Sexp.Listts->M.List.iterts~f:(load_includesvisitedfile)|Sexp.Atom_->M.return()inload[]file>>=fun()->M.return!file_contentsletload_all_annotated_includesfile_contents:Sexp.Annotated.tlistfile_contentsM.t=M.List.mapfile_contents~f:(fun(file,_)->S.load_annotated_sexpsfile>>=funts->M.return(file,ts))letfind_annotatedbad_sexpannot_file_contents=List.find_mapannot_file_contents~f:(fun(file,annot_sexps)->List.find_mapannot_sexps~f:(funannot_sexp->matchSexp.Annotated.find_sexpannot_sexpbad_sexpwith|None->None|Someannot_sexp->Some(file,annot_sexp)))(* This function has to compute a transformation trail even though all of the returned
errors are of the form [Of_sexp_error (_, t)] where [t] is a physical subexpression of
the input, in the event where an error happens not during macro expansion but during
conversion to ocaml values. *)letexpand_and_convert~multiple(mode:mode)filef=lettrail=ref([]:trail)inletadd_result~arg~result=matchmodewith|`Fast_->()|`Find_error_->trail:=(result,arg)::!trailinletfile_contents=matchmodewith|`Fastfile_contents->file_contents|`Find_errorannot_file_contents->List.map~f:(fun(file,annot_sexps)->(file,List.map~f:Sexp.Annotated.get_sexpannot_sexps))annot_file_contentsinletrecinline_includescurrent_file=function|Sexp.Atom_ast->[t](* We expand an :include in list context, because that corresponds to
the naive string substitution semantics. *)|Sexp.List[Sexp.Atom":include";Sexp.Atominclude_file]->load_and_inline(make_absolute_path~with_respect_to:current_fileinclude_file)|Sexp.Listtsast->letts=List.concat_mapts~f:(inline_includescurrent_file)inlett'=Sexp.Listtsinadd_result~arg:t~result:t';[t']andload_and_inlinefile=(* The lookup always succeeds, because [file_contents] is a result of
[load_all_includes]. *)letts=List.concat_map(List.assocfilefile_contents)~f:(inline_includesfile)in(* This checks that, after expanding the includes of file1, file1 doesn't
have any free variables. So if file1 is included in file2, it won't use
any of the variable of file2 in scope where file1 is included.
However, the inclusion of file1 may shadow variables from file2. *)check_no_free_variablests;tsinletmap_resultsts~f=ifmultiplethenList.map~ftselsematchtswith|[t]->[ft]|ts->failwith(sprintf"wrong number of sexps in %s, expecting 1, got %d"file(List.lengthts))inmatchmodewith|`Fast_->letts=expand_local_macros_exn~trail:None(load_and_inlinefile)inmap_resultsts~f:(funt->`Result(ft))|`Find_errorannot_file_contents->letlocate_errorf=try`Result(f())withOf_sexp_error(exc,bad_sexp)ase->(* Find the original sexp that caused the error. *)letunexpanded_bad_sexp=find_argbad_sexp!trailinmatchfind_annotatedunexpanded_bad_sexpannot_file_contentswith|Some(file,unexpanded_bad_annot_sexp)->letexc=matchSexp.Annotated.get_conv_exn~file~excunexpanded_bad_annot_sexpwith|Of_sexp_error(inner_exc,unexpanded_bad_sexp)asexc->ifbad_sexp=unexpanded_bad_sexpthenexcelseMacro_conv_error(inner_exc,unexpanded_bad_sexp,`expandedbad_sexp)|exc->excin`Error(exc,unexpanded_bad_annot_sexp)(* This case should never happen. *)|None->raiseeinletinline_and_expand()=expand_local_macros_exn~trail:(Sometrail)(load_and_inlinefile)inmatchlocate_errorinline_and_expandwith|`Error_ase->[e]|`Resultts->map_resultsts~f:(funt->locate_error(fun()->ft))letload~multiplefilef=load_all_includesfile>>=funfile_contents->tryM.return(expand_and_convert~multiple(`Fastfile_contents)filef)withOf_sexp_error_asoriginal_exn->beginload_all_annotated_includesfile_contents>>=funannotated_file_contents->letresult=(expand_and_convert~multiple(`Find_errorannotated_file_contents)filef)inifList.existsresult~f:(function|`Result_->false|`Error_->true)thenM.returnresultelse(* Avoid returning success in the case there was an error.
This can be bad e.g. when reading the input from a pipe. *)raiseoriginal_exnendletload_sexps_convfilef=load~multiple:truefilefletload_sexp_convfilef=load~multiple:falsefilef>>=function|[a]->M.returna|_->assertfalseendexceptionError_in_fileofstring*exnlet()=Conv.Exn_converter.add~finalise:false[%extension_constructorError_in_file](function|Error_in_file(file,exn)->Sexp.List[Sexp.Atom("Error in file "^file);Conv.sexp_of_exnexn]|_->assertfalse)letadd_error_locationfile=function|Sexp.Parse_errore->leterr_msg=sprintf"%s: %s"filee.Sexp.err_msginSexp.Parse_error{ewithSexp.err_msg}|Failuree->Failure(sprintf"%s: %s"filee)|error->Error_in_file(file,error)moduleSimple_sexp_loader=structmoduleMonad=structtype'at='aletreturna=amoduleMonad_infix=structlet(>>=)af=faendmoduleList=Listendletload_sexpsfile=trySexp.load_sexpsfilewithe->raise(add_error_locationfilee)letload_annotated_sexpsfile=trySexp.Annotated.load_sexpsfilewithe->raise(add_error_locationfilee)endmoduleSimple_loader=Loader(Simple_sexp_loader)letida=aletload_sexp_conv=Simple_loader.load_sexp_convletload_sexp_conv_exnfilef=matchload_sexp_convfilefwith|`Resulta->a|`Error(exn,_)->raiseexnletload_sexpfile=load_sexp_conv_exnfileidletload_sexps_conv=Simple_loader.load_sexps_convletload_sexps_conv_exnfilef=letresults=load_sexps_convfilefinList.mapresults~f:(function|`Error(exn,_)->raiseexn|`Resulta->a)letload_sexpsfile=load_sexps_conv_exnfileid