123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266(************************************************************************)(* 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.hashletequal=Coq.State.equalendmoduleEnv=structtypet=Fleche.Doc.Env.tend(** Petanque errors *)moduleError=structtypet=|Interrupted|Parsingofstring|Coqofstring|Anomalyofstring|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|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|Theorem_not_found_->-32005endmoduleR=structtype'at=('a,Error.t)Result.tendmoduleRun_result=structtype'at=|Proof_finishedof'a|Current_stateof'aendletinit_coq~debug=letload_module=Dynlink.loadfileinletload_plugin=Coq.Loader.plugin_handlerNoneinCoq.Init.(coq_init{debug;load_module;load_plugin})letcmdline:Coq.Workspace.CmdLine.t={coqlib=Coq_config.coqlib;coqcorelib=Filename.concatCoq_config.coqlib"../coq-core";ocamlpath=None;vo_load_path=[];ml_include_path=[];args=[];require_libraries=[]}lettrace_stderrhdr?extra:_msg=Format.eprintf"@[[trace] %s | %s @]@\n%!"hdrmsglettrace_ref=reftrace_stderrletmessage_stderr~lvl:_~message=Format.eprintf"@[[message] %s @]@\n%!"messageletmessage_ref=refmessage_stderrletio=lettracehdr?extramsg=!trace_refhdr?extramsginletmessage~lvl~message=!message_ref~lvl~messageinletdiagnostics~uri:_~version:__diags=()inletfileProgress~uri:_~version:__pinfo=()inletperfData~uri:_~version:__perf=()inletserverVersion_=()inletserverStatus_=()in{Fleche.Io.CallBack.trace;message;diagnostics;fileProgress;perfData;serverVersion;serverStatus}letread_raw~uri=letfile=Lang.LUri.File.to_string_fileuriintryOkCoq.Compat.Ocaml_414.In_channel.(with_open_textfileinput_all)withSys_errorerr->Errorerrletfind_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 *)Ok(Fleche.Doc.Node.statenode)letpp_diagfmt{Lang.Diagnostic.message;_}=Format.fprintffmt"%a"Pp.pp_withmessageletprint_diags(doc:Fleche.Doc.t)=letd=Fleche.Doc.diagsdocinFormat.(eprintf"@[<v>%a@]"(pp_print_listpp_diag)d)letinit~token~debug~root=letinit=init_coq~debuginFleche.Io.CallBack.setio;letdir=Lang.LUri.File.to_string_filerootin(letopenCoq.Compat.Result.Oinlet+workspace=Coq.Workspace.guess~token~debug~cmdline~dirinletfiles=Coq.Files.make()inFleche.Doc.Env.make~init~workspace~files)|>Result.map_error(funmsg->Error.Coqmsg)letstart~token~env~uri~thm=matchread_raw~uriwith|Okraw->(* Format.eprintf "raw: @[%s@]%!" raw; *)letdoc=Fleche.Doc.create~token~env~uri~version:0~rawinprint_diagsdoc;lettarget=Fleche.Doc.Target.Endinletdoc=Fleche.Doc.check~io~token~target~doc()infind_thm~doc~thm|Errorerr->letmsg=Format.asprintf"@[[read_raw] File not found %s@]"errinError(Error.Theorem_not_foundmsg)letparse~loctacst=letstr=Gramlib.Stream.of_stringtacinletstr=Coq.Parsing.Parsable.make?locstrinCoq.Parsing.parse~ststrletproof_finished{Coq.Goals.goals;stack;shelf;given_up;_}=List.for_allCList.is_empty[goals;shelf;given_up]&&CList.is_emptystackletparse_and_execute_in~token~loctacst=letopenCoq.Protect.E.Oinlet*ast=parse~token~loctacstinmatchastwith|Someast->(letopenCoq.Protect.E.Oinlet*st=Fleche.Memo.Interp.eval~token(st,ast)inlet+goals=Fleche.Info.Goals.goals~token~stinmatchgoalswith|None->Run_result.Proof_finishedst|Somegoalswhenproof_finishedgoals->Run_result.Proof_finishedst|_->Run_result.Current_statest)|None->Coq.Protect.E.ok(Run_result.Current_statest)letprotect_to_result(r:_Coq.Protect.E.t):(_,_)Result.t=matchrwith|{r=Interrupted;feedback=_}->ErrorError.Interrupted|{r=Completed(Error(User(_loc,msg)));feedback=_}->Error(Error.Coq(Pp.string_of_ppcmdsmsg))|{r=Completed(Error(Anomaly(_loc,msg)));feedback=_}->Error(Error.Anomaly(Pp.string_of_ppcmdsmsg))|{r=Completed(Okr);feedback=_}->Okrletrun_tac~token~st~tac:(_Run_result.t,Error.t)Result.t=(* Improve with thm? *)letloc=NoneinCoq.State.in_stateM~token~st~f:(parse_and_execute_in~token~loctac)st|>protect_to_resultletgoals~token~st=letfgoals=letf=Coq.Goals.map_reified_goal~f:Pp.string_of_ppcmdsinletg=Pp.string_of_ppcmdsinOption.map(Coq.Goals.map_goals~f~g)goalsinCoq.Protect.E.map~f(Fleche.Info.Goals.goals~token~st)|>protect_to_resultmodulePremise=structtypet={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 *);kind:(string,string)Result.t(* type of object *);range:(Lang.Range.t,string)Result.t(* a range if known *);offset:(int*int,string)Result.t(* a offset in the file if known (from .glob files) *);raw_text:(string,string)Result.t(* raw text of the premise *)}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_fileglobinlet+{Coq.Glob.Info.kind;offset}=Coq.Glob.get_infognamein(kind,offset)letraw_of~file~offset=matchoffsetwith|Ok(bp,ep)->letopenCoq.Compat.Result.Oinlet*c=Memo.input_sourcefileinifString.lengthc<epthenError"offset out of bounds"elseOk(String.subcbp(ep-bp+1))|Errorerr->Error("offset information is not available: "^err)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")inletrange=Error"not implemented yet"inletkind,offset=info_of~glob~name|>Coq.Compat.Result.splitinletraw_text=raw_of~file~offsetin{full_name=name;file;kind;range;offset;raw_text}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