123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287(************************************************************************)(* 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 *)(************************************************************************)letpet_debug=falsemoduleState=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_foundofstringletto_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(* JSON-RPC server reserved codes *)letto_code=function|Interrupted->-32001|Parsing_->-32002|Coq_->-32003|Anomaly_->-32004|System_->-32005|Theorem_not_found_->-32006letcoqe=Coqeletsysteme=SystemeendmoduleR=structtype'at=('a,Error.t)Result.tendmoduleRun_opts=structtypet={memo:bool[@defaulttrue];hash:bool[@defaulttrue]}endmoduleRun_result=structtype'at={st:'a;hash:intoption[@defaultNone];proof_finished:bool}endletfind_thm~(doc:Fleche.Doc.t)~thm=let{Fleche.Doc.toc;_}=docinmatchCString.Map.find_optthmtocwith|None->letmsg=Format.asprintf"@[[find_thm] Theorem not found!@]"inError(Error.Theorem_not_foundmsg)|Somenode->ifpet_debugthenFormat.eprintf"@[[find_thm] Theorem found!@\n@]%!";(* let point = (range.start.line, range.start.character) in *)Oknodeletexecute_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.stateletprotect_to_result(r:_Coq.Protect.E.t):(_,_)Result.t=matchrwith|{r=Interrupted;feedback=_}->ErrorError.Interrupted|{r=Completed(Error(User{msg;_}));feedback=_}->Error(Error.Coq(Pp.string_of_ppcmdsmsg))|{r=Completed(Error(Anomaly{msg;_}));feedback=_}->Error(Error.Anomaly(Pp.string_of_ppcmdsmsg))|{r=Completed(Okr);feedback=_}->Okrletproof_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.Proofletanalyze_after_run~hashst=letproof_finished=letgoals=Fleche.Info.Goals.get_goals_unit~stinmatchgoalswith|None->true|Somegoalswhenproof_finishedgoals->true|_->falseinlethash=ifhashthenHash_kind.hash~kind:hash_modestelseNoneinRun_result.{st;hash;proof_finished}(* Would be nice to keep this in sync with the type annotations. *)letdefault_opts=function|None->{Run_opts.memo=true;hash=true}|Someopts->optsletstart~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)Result.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_ppcmdsinOption.map(Coq.Goals.map~f~g)goalsinCoq.Protect.E.map~f(Fleche.Info.Goals.goals~token~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_libsinList.mapto_premiseall_premises)|>protect_to_result