123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357(************************************************************************)(* Flèche => RL agent: petanque *)(* Copyright 2019 MINES ParisTech -- Dual License LGPL 2.1 / GPL3+ *)(* Copyright 2019-2024 Inria -- Dual License LGPL 2.1 / GPL3+ *)(* Written by: Emilio J. Gallego Arias & coq-lsp contributors *)(************************************************************************)moduleState=structtypet=Coq.State.tlethash=Coq.State.hashletname="state"moduleInspect=structtypet=|Physical(** Flèche-based "almost physical" state eq *)|Goals(** Full goal equality; must faster than calling goals as it won't
unelaborate them. Note that this may not fully capture proof state
equality (it is possible to have similar goals but different
evar_maps, but should be enough for all practical users. *)endletequal?(kind=Inspect.Physical)=matchkindwith|Physical->Coq.State.equal|Goals->funst1st2->letst1,st2=(Coq.State.lemmas~st:st1,Coq.State.lemmas~st:st2)inOption.equalCoq.Goals.Equality.equal_goalsst1st2moduleProof=structtypet=Coq.State.Proof.tletequal?(kind=Inspect.Physical)=matchkindwith|Physical->Coq.State.Proof.equal|Goals->Coq.Goals.Equality.equal_goalslethash=Coq.State.Proof.hashendletlemmasst=Coq.State.lemmas~stend(** Petanque errors *)moduleError=structtypet=|Interrupted|Parsingofstring|Coqofstring|Anomalyofstring|Systemofstring|Theorem_not_foundofstring|No_node_at_pointletto_string=function|Interrupted->Format.asprintf"Interrupted"|Parsingmsg->Format.asprintf"Parsing: %s"msg|Coqmsg->Format.asprintf"Coq: %s"msg|Anomalymsg->Format.asprintf"Anomaly: %s"msg|Systemmsg->Format.asprintf"System: %s"msg|Theorem_not_foundmsg->Format.asprintf"Theorem_not_found: %s"msg|No_node_at_point->Format.asprintf"No node at point"(* JSON-RPC server reserved codes *)letto_code=function|Interrupted->-32001|Parsing_->-32002|Coq_->-32003|Anomaly_->-32004|System_->-32005|Theorem_not_found_->-32006|No_node_at_point->-32007letcoqe=Coqeletsysteme=Systemeletmakee~feedback=Request.Error.make(to_codee)e~feedbackletmake_requeste=makee~feedback:[]endmoduleR=structtype'at=('a,Error.t)Request.R.tendmoduleRun_opts=structtypet={memo:bool[@defaulttrue];hash:bool[@defaulttrue]}endmoduleRun_result=structtype'at={st:'a;hash:intoption[@defaultNone];proof_finished:bool;feedback:(int*string)list}endletfind_thm~(doc:Fleche.Doc.t)~thm=let{Fleche.Doc.toc;_}=docinletfeedback=[]inmatchCString.Map.find_optthmtocwith|None->letmsg=Format.asprintf"@[[find_thm] Theorem not found!@]"inErrorError.(make(Theorem_not_foundmsg)~feedback)|Somenode->((* If the node has an error we cannot assume the state is valid *)matchList.filterLang.Diagnostic.is_errornode.diagswith|[]->Oknode|err::_->letmsg=Format.asprintf"@[[find_thm] Theorem found but failed with Coq error:@\n @[%a@]!@]"Pp.pp_witherr.messageinErrorError.(make(Theorem_not_foundmsg)~feedback))letexecute_precommands~token~memo~pre_commands~(node:Fleche.Doc.Node.t)=match(pre_commands,node.prev,node.ast)with|Somepre_commands,Someprev,Someast->letst=prev.stateinletopenCoq.Protect.E.Oinlet*st=Fleche.Doc.run~token~memo?loc:None~stpre_commandsin(* We re-interpret the lemma statement *)Fleche.Memo.Interp.eval~token(st,ast.v)|_,_,_->Coq.Protect.E.oknode.state(* XXX Fix better by making protect errors and request errors share the loc
type, so we can execute with Coq locations *)letclean_fbfbs=List.map(fun(lvl,{Coq.Message.Payload.msg;_})->(lvl,{Coq.Message.Payload.range=None;quickFix=None;msg}))fbsletprotect_to_result(r:_Coq.Protect.E.t):(_,_)Result.t=matchrwith|{r=Interrupted;feedback}->letfeedback=clean_fbfeedbackinErrorError.(makeInterrupted~feedback)|{r=Completed(Error(User{msg;_}));feedback}->letfeedback=clean_fbfeedbackinErrorError.(make(Coq(Pp.string_of_ppcmdsmsg))~feedback)|{r=Completed(Error(Anomaly{msg;_}));feedback}->letfeedback=clean_fbfeedbackinErrorError.(make(Anomaly(Pp.string_of_ppcmdsmsg))~feedback)|{r=Completed(Okr);feedback}->Ok(rfeedback)letproof_finished{Coq.Goals.goals;stack;shelf;given_up;_}=letcheck_stackstack=CList.(for_all(fun(l,r)->is_emptyl&&is_emptyr))stackinList.for_allCList.is_empty[goals;shelf;given_up]&&check_stackstack(* At some point we want to return both hashes *)moduleHash_kind=structtypet=|Full|Proof[@@warning"-37"]lethash~kindst=matchkindwith|Full->Some(State.hashst)|Proof->Option.mapState.Proof.hash(State.lemmasst)endlethash_mode=Hash_kind.Proof(* XXX: duplicated with Request.R.of_execution, refactoring planned (not
trivial) *)letfb_print_string(lvl,{Coq.Message.Payload.msg;_})=(lvl,Pp.string_of_ppcmdsmsg)letanalyze_after_run~hashstfeedback=letproof_finished=letgoals=Fleche.Info.Goals.get_goals_unit~stinmatchgoalswith|None->true|Somegoalswhenproof_finishedgoals->true|_->falseinlethash=ifhashthenHash_kind.hash~kind:hash_modestelseNoneinletfeedback=List.mapfb_print_stringfeedbackinRun_result.{st;hash;proof_finished;feedback}(* Would be nice to keep this in sync with the type annotations. *)letdefault_opts=function|None->{Run_opts.memo=true;hash=true}|Someopts->optsletget_root_state?opts~doc()=letopts=default_optsoptsinlethash=opts.hashinletstate=doc.Fleche.Doc.rootinOk(analyze_after_run~hashstate[])letget_state_at_pos?opts~doc~point()=matchFleche.Info.(LC.node~doc~pointExact)with|Some{Fleche.Doc.Node.state;_}->letopts=default_optsoptsinlethash=opts.hashinOk(analyze_after_run~hashstate[])|None->Error(Error.make_requestNo_node_at_point)letstart~token~doc?opts?pre_commands~thm()=letopenCoq.Compat.Result.Oinlet*node=find_thm~doc~thmin(* Usually single shot, so we don't memoize *)letopts=default_optsoptsinletmemo,hash=(opts.memo,opts.hash)inletexecution=letopenCoq.Protect.E.Oinlet+st=execute_precommands~token~memo~pre_commands~nodein(* Note this runs on the resulting state, anyways it is purely functional *)analyze_after_run~hashstinprotect_to_resultexecutionletrun~token?opts~st~tac():(_Run_result.t,Error.t)Request.R.t=letopts=default_optsoptsin(* Improve with thm? *)letmemo,hash=(opts.memo,opts.hash)inletexecution=letopenCoq.Protect.E.Oinlet+st=Fleche.Doc.run~token~memo?loc:None~sttacin(* Note this runs on the resulting state, anyways it is purely functional *)analyze_after_run~hashstinprotect_to_resultexecutionletgoals~token~st=letfgoals=letf=Coq.Goals.Reified_goal.map~f:Pp.string_of_ppcmdsinletg=Pp.string_of_ppcmdsin(* XXX: Fixme, take feedback into account *)fun_feedback->Option.map(Coq.Goals.map~f~g)goalsinletpr=Fleche.Info.Goals.to_ppinCoq.Protect.E.map~f(Fleche.Info.Goals.goals~token~pr~st)|>protect_to_resultmodulePremise=structmoduleInfo=structtypet={kind:string(* type of object *);range:Lang.Range.toption(* a range *);offset:int*int(* a offset in the file *);raw_text:(string,string)Result.t(* raw text of the premise *)}endtypet={full_name:string(* should be a Coq DirPath, but let's go step by step *);file:string(* file (in FS format) where the premise is found *);info:(Info.t,string)Result.t(* Info about the object, if available *)}end(* We need some caching here otherwise it is very expensive to re-parse the glob
files all the time.
XXX move this caching to Flèche. *)moduleMemo=structmoduleH=Hashtbl.Make(CString)lettable_glob=H.create1000letopen_fileglob=matchH.find_opttable_globglobwith|Someg->g|None->letg=Coq.Glob.open_fileglobinH.addtable_globglobg;glettable_source=H.create1000letinput_sourcefile=matchH.find_opttable_sourcefilewith|Someres->res|None->ifSys.file_existsfilethen(letres=OkCoq.Compat.Ocaml_414.In_channel.(with_open_textfileinput_all)inH.addtable_sourcefileres;res)elseletres=Error"source file is not available"inH.addtable_sourcefileres;resendletinfo_of~glob~name=letopenCoq.Compat.Result.Oinlet*g=Memo.open_fileglobinOk(Option.map(fun{Coq.Glob.Info.kind;offset}->(kind,offset))(Coq.Glob.get_infogname))letraw_of~file~offset=letbp,ep=offsetinletopenCoq.Compat.Result.Oinlet*c=Memo.input_sourcefileinifString.lengthc<epthenError"offset out of bounds"elseOk(String.subcbp(ep-bp+1))letto_premise(p:Coq.Library_file.Entry.t):Premise.t=let{Coq.Library_file.Entry.name;typ=_;file}=pinletfile=Filename.(remove_extensionfile^".v")inletglob=Filename.(remove_extensionfile^".glob")inletinfo=matchinfo_of~glob~namewith|OkNone->Error"not in glob table"|Errorerr->Errorerr|Ok(Some(kind,offset))->letrange=Noneinletraw_text=raw_of~file~offsetinOk{Premise.Info.kind;range;offset;raw_text}in{Premise.full_name=name;file;info}letpremises~token~st=(letopenCoq.Protect.E.Oinlet*all_libs=Coq.Library_file.loaded~token~stinlet+all_premises=Coq.Library_file.toc~token~stall_libsin(* XXX: Fixme, take feedback into account *)fun_feedback->List.mapto_premiseall_premises)|>protect_to_resultletsimple_run_resultresfeedback=letproof_finished=falseinlethash=Noneinletfeedback=List.mapfb_print_stringfeedbackinRun_result.{st=res;hash;proof_finished;feedback}letast~token~st~text():Coq.Ast.toptionRun_result.tR.t=(letopenCoq.Protect.E.Oinletpa=Coq.Parsing.Parsable.makeGramlib.Stream.(of_stringtext)inlet+ast=Coq.Parsing.parse~token~stpainsimple_run_resultast)|>protect_to_resultletast_at_pos~doc~point()=matchFleche.Info.(LC.node~doc~pointExact)with|Some{Fleche.Doc.Node.ast;_}->Ok(Option.map(funast->ast.Fleche.Doc.Node.Ast.v)ast)|None->Error(Error.make_requestNo_node_at_point)(* See PROTOCOL.md for details on versioning *)letversion=2