summaryrefslogtreecommitdiff
path: root/ide/ide_slave.ml
diff options
context:
space:
mode:
Diffstat (limited to 'ide/ide_slave.ml')
-rw-r--r--ide/ide_slave.ml317
1 files changed, 145 insertions, 172 deletions
diff --git a/ide/ide_slave.ml b/ide/ide_slave.ml
index 5b07d3ec..6b7efc83 100644
--- a/ide/ide_slave.ml
+++ b/ide/ide_slave.ml
@@ -1,23 +1,27 @@
(************************************************************************)
-
-(* v * The Coq Proof Assistant / The Coq Development Team *)
-(* <O___,, * INRIA - CNRS - LIX - LRI - PPS - Copyright 1999-2016 *)
+(* * The Coq Proof Assistant / The Coq Development Team *)
+(* v * INRIA, CNRS and contributors - Copyright 1999-2018 *)
+(* <O___,, * (see CREDITS file for the list of authors) *)
(* \VV/ **************************************************************)
-(* // * This file is distributed under the terms of the *)
-(* * GNU Lesser General Public License Version 2.1 *)
+(* // * This file is distributed under the terms of the *)
+(* * GNU Lesser General Public License Version 2.1 *)
+(* * (see LICENSE file for the text of the license) *)
(************************************************************************)
open Vernacexpr
+open Vernacprop
open CErrors
open Util
open Pp
open Printer
+module NamedDecl = Context.Named.Declaration
+module CompactedDecl = Context.Compacted.Declaration
+
(** Ide_slave : an implementation of [Interface], i.e. mainly an interp
function and a rewind function. This specialized loop is triggered
- when the -ideslave option is passed to Coqtop. Currently CoqIDE is
- the only one using this mode, but we try here to be as generic as
- possible, so this may change in the future... *)
+ when the -ideslave option is passed to Coqtop. *)
+
(** Signal handling: we postpone ^C during input and output phases,
but make it directly raise a Sys.Break during evaluation of the request. *)
@@ -28,24 +32,6 @@ let init_signal_handler () =
let f _ = if !catch_break then raise Sys.Break else Control.interrupt := true in
Sys.set_signal Sys.sigint (Sys.Signal_handle f)
-
-(** Redirection of standard output to a printable buffer *)
-
-let init_stdout, read_stdout =
- let out_buff = Buffer.create 100 in
- let out_ft = Format.formatter_of_buffer out_buff in
- let deep_out_ft = Format.formatter_of_buffer out_buff in
- let _ = Pp_control.set_gp deep_out_ft Pp_control.deep_gp in
- (fun () ->
- flush_all ();
- Pp_control.std_ft := out_ft;
- Pp_control.err_ft := out_ft;
- Pp_control.deep_ft := deep_out_ft;
- ),
- (fun () -> Format.pp_print_flush out_ft ();
- let r = Buffer.contents out_buff in
- Buffer.clear out_buff; r)
-
let pr_with_pid s = Printf.eprintf "[pid %d] %s\n%!" (Unix.getpid ()) s
let pr_error s = pr_with_pid s
@@ -67,73 +53,85 @@ let coqide_known_option table = List.mem table [
["Printing";"All"];
["Printing";"Records"];
["Printing";"Existential";"Instances"];
- ["Printing";"Universes"]]
-
-let is_known_option cmd = match cmd with
- | VernacSetOption (o,BoolValue true)
- | VernacUnsetOption o -> coqide_known_option o
- | _ -> false
-
-let is_debug cmd = match cmd with
- | VernacSetOption (["Ltac";"Debug"], _) -> true
- | _ -> false
-
-let is_query cmd = match cmd with
- | VernacChdir None
- | VernacMemOption _
- | VernacPrintOption _
- | VernacCheckMayEval _
- | VernacGlobalCheck _
- | VernacPrint _
- | VernacSearch _
- | VernacLocate _ -> true
- | _ -> false
+ ["Printing";"Universes"];
+ ["Printing";"Unfocused"]]
-let is_undo cmd = match cmd with
- | VernacUndo _ | VernacUndoTo _ -> true
+let is_known_option cmd = match Vernacprop.under_control cmd with
+ | VernacSetOption (_, o, BoolValue true)
+ | VernacUnsetOption (_, o) -> coqide_known_option o
| _ -> false
-(** Check whether a command is forbidden by CoqIDE *)
+(** Check whether a command is forbidden in the IDE *)
-let coqide_cmd_checks (loc,ast) =
- let user_error s = CErrors.user_err_loc (loc, "CoqIde", str s) in
+let ide_cmd_checks ~id {CAst.loc;v=ast} =
+ let user_error s = CErrors.user_err ?loc ~hdr:"IDE" (str s) in
+ let warn msg = Feedback.(feedback ~id (Message (Warning, loc, strbrk msg))) in
if is_debug ast then
- user_error "Debug mode not available within CoqIDE";
+ user_error "Debug mode not available in the IDE";
if is_known_option ast then
- Feedback.msg_warning (strbrk"This will not work. Use CoqIDE view menu instead");
- if Vernac.is_navigation_vernac ast || is_undo ast then
- Feedback.msg_warning (strbrk "Rather use CoqIDE navigation instead");
- if is_query ast then
- Feedback.msg_warning (strbrk "Query commands should not be inserted in scripts")
+ warn "Set this option from the IDE menu instead";
+ if is_navigation_vernac ast || is_undo ast then
+ warn "Use IDE navigation instead"
(** Interpretation (cf. [Ide_intf.interp]) *)
+let ide_doc = ref None
+let get_doc () = Option.get !ide_doc
+let set_doc doc = ide_doc := Some doc
+
let add ((s,eid),(sid,verbose)) =
- let newid, rc = Stm.add ~ontop:sid verbose ~check:coqide_cmd_checks eid s in
+ let doc = get_doc () in
+ let pa = Pcoq.Gram.parsable (Stream.of_string s) in
+ let loc_ast = Stm.parse_sentence ~doc sid pa in
+ let doc, newid, rc = Stm.add ~doc ~ontop:sid verbose loc_ast in
+ set_doc doc;
let rc = match rc with `NewTip -> CSig.Inl () | `Unfocus id -> CSig.Inr id in
- newid, (rc, read_stdout ())
+ ide_cmd_checks ~id:newid loc_ast;
+ (* TODO: the "" parameter is a leftover of the times the protocol
+ * used to include stderr/stdout output.
+ *
+ * Currently, we force all the output meant for the to go via the
+ * feedback mechanism, and we don't manipulate stderr/stdout, which
+ * are left to the client's discrection. The parameter is still there
+ * as not to break the core protocol for this minor change, but it should
+ * be removed in the next version of the protocol.
+ *)
+ newid, (rc, "")
let edit_at id =
- match Stm.edit_at id with
- | `NewTip -> CSig.Inl ()
- | `Focus { Stm.start; stop; tip} -> CSig.Inr (start, (stop, tip))
-
-let query (s,id) = Stm.query ~at:id s; read_stdout ()
+ let doc = get_doc () in
+ match Stm.edit_at ~doc id with
+ | doc, `NewTip -> set_doc doc; CSig.Inl ()
+ | doc, `Focus { Stm.start; stop; tip} -> set_doc doc; CSig.Inr (start, (stop, tip))
+
+(* TODO: the "" parameter is a leftover of the times the protocol
+ * used to include stderr/stdout output.
+ *
+ * Currently, we force all the output meant for the to go via the
+ * feedback mechanism, and we don't manipulate stderr/stdout, which
+ * are left to the client's discrection. The parameter is still there
+ * as not to break the core protocol for this minor change, but it should
+ * be removed in the next version of the protocol.
+ *)
+let query (route, (s,id)) =
+ let pa = Pcoq.Gram.parsable (Stream.of_string s) in
+ let doc = get_doc () in
+ Stm.query ~at:id ~doc ~route pa
let annotate phrase =
- let (loc, ast) =
+ let doc = get_doc () in
+ let {CAst.loc;v=ast} =
let pa = Pcoq.Gram.parsable (Stream.of_string phrase) in
- Vernac.parse_sentence (pa,None)
+ Stm.parse_sentence ~doc (Stm.get_current_state ~doc) pa
in
- let (_, xml) =
- Richprinter.richpp_vernac ast
- in
- xml
+ (* XXX: Width should be a parameter of annotate... *)
+ Richpp.richpp_of_pp 78 (Ppvernac.pr_vernac ast)
(** Goal display *)
let hyp_next_tac sigma env decl =
- let (id,_,ast) = Context.Named.Declaration.to_tuple decl in
+ let id = NamedDecl.get_id decl in
+ let ast = NamedDecl.get_type decl in
let id_s = Names.Id.to_string id in
let type_s = string_of_ppcmds (pr_ltype_env env sigma ast) in
[
@@ -186,20 +184,14 @@ let process_goal sigma g =
let min_env = Environ.reset_context env in
let id = Goal.uid g in
let ccl =
- let norm_constr = Reductionops.nf_evar sigma (Goal.V82.concl sigma g) in
- Richpp.richpp_of_pp (pr_goal_concl_style_env env sigma norm_constr)
+ pr_goal_concl_style_env env sigma (Goal.V82.concl sigma g)
in
let process_hyp d (env,l) =
- let d = Context.NamedList.Declaration.map_constr (Reductionops.nf_evar sigma) d in
- let d' = List.map (fun name -> let open Context.Named.Declaration in
- match pi2 d with
- | None -> LocalAssum (name, pi3 d)
- | Some value -> LocalDef (name, value, pi3 d))
- (pi1 d) in
+ let d' = CompactedDecl.to_named_context d in
(List.fold_right Environ.push_named d' env,
- (Richpp.richpp_of_pp (pr_var_list_decl env sigma d)) :: l) in
+ (pr_compacted_decl env sigma d) :: l) in
let (_env, hyps) =
- Context.NamedList.fold process_hyp
+ Context.Compacted.fold process_hyp
(Termops.compact_named_context (Environ.named_context env)) ~init:(min_env,[]) in
{ Interface.goal_hyp = List.rev hyps; Interface.goal_ccl = ccl; Interface.goal_id = id; }
@@ -212,9 +204,8 @@ let export_pre_goals pgs =
}
let goals () =
- Stm.finish ();
- let s = read_stdout () in
- if not (String.is_empty s) then Feedback.msg_info (str s);
+ let doc = get_doc () in
+ set_doc @@ Stm.finish ~doc;
try
let pfts = Proof_global.give_me_the_proof () in
Some (export_pre_goals (Proof.map_structured_proof pfts process_goal))
@@ -222,12 +213,11 @@ let goals () =
let evars () =
try
- Stm.finish ();
- let s = read_stdout () in
- if not (String.is_empty s) then Feedback.msg_info (str s);
+ let doc = get_doc () in
+ set_doc @@ Stm.finish ~doc;
let pfts = Proof_global.give_me_the_proof () in
- let { Evd.it = all_goals ; sigma = sigma } = Proof.V82.subgoals pfts in
- let exl = Evar.Map.bindings (Evarutil.non_instantiated sigma) in
+ let all_goals, _, _, _, sigma = Proof.proof pfts in
+ let exl = Evar.Map.bindings (Evd.undefined_map sigma) in
let map_evar ev = { Interface.evar_info = string_of_ppcmds (pr_evar sigma ev); } in
let el = List.map map_evar exl in
Some el
@@ -236,7 +226,7 @@ let evars () =
let hints () =
try
let pfts = Proof_global.give_me_the_proof () in
- let { Evd.it = all_goals ; sigma = sigma } = Proof.V82.subgoals pfts in
+ let all_goals, _, _, _, sigma = Proof.proof pfts in
match all_goals with
| [] -> None
| g :: _ ->
@@ -250,14 +240,17 @@ let hints () =
(** Other API calls *)
+let wait () =
+ let doc = get_doc () in
+ set_doc (Stm.wait ~doc)
+
let status force =
(** We remove the initial part of the current [DirPath.t]
(usually Top in an interactive session, cf "coqtop -top"),
and display the other parts (opened sections and modules) *)
- Stm.finish ();
- if force then Stm.join ();
- let s = read_stdout () in
- if not (String.is_empty s) then Feedback.msg_info (str s);
+ set_doc (Stm.finish ~doc:(get_doc ()));
+ if force then
+ set_doc (Stm.join ~doc:(get_doc ()));
let path =
let l = Names.DirPath.repr (Lib.cwd ()) in
List.rev_map Names.Id.to_string l
@@ -274,13 +267,13 @@ let status force =
Interface.status_path = path;
Interface.status_proofname = proof;
Interface.status_allproofs = allproofs;
- Interface.status_proofnum = Stm.current_proof_depth ();
+ Interface.status_proofnum = Stm.current_proof_depth ~doc:(get_doc ());
}
let export_coq_object t = {
Interface.coq_object_prefix = t.Search.coq_object_prefix;
Interface.coq_object_qualid = t.Search.coq_object_qualid;
- Interface.coq_object_object = t.Search.coq_object_object
+ Interface.coq_object_object = Pp.string_of_ppcmds (pr_lconstr_env (Global.env ()) Evd.empty t.Search.coq_object_object)
}
let pattern_of_string ?env s =
@@ -290,17 +283,17 @@ let pattern_of_string ?env s =
| Some e -> e
in
let constr = Pcoq.parse_string Pcoq.Constr.lconstr_pattern s in
- let (_, pat) = Constrintern.intern_constr_pattern env constr in
+ let (_, pat) = Constrintern.intern_constr_pattern env Evd.empty constr in
pat
let dirpath_of_string_list s =
let path = String.concat "." s in
let m = Pcoq.parse_string Pcoq.Constr.global path in
- let (_, qid) = Libnames.qualid_of_reference m in
+ let {CAst.v=qid} = Libnames.qualid_of_reference m in
let id =
try Nametab.full_name_module qid
with Not_found ->
- CErrors.errorlabstrm "Search.interface_search"
+ CErrors.user_err ~hdr:"Search.interface_search"
(str "Module " ++ str path ++ str " not found.")
in
id
@@ -330,7 +323,7 @@ let import_option_value = function
| Interface.StringOptValue s -> Goptions.StringOptValue s
let export_option_state s = {
- Interface.opt_sync = s.Goptions.opt_sync;
+ Interface.opt_sync = true;
Interface.opt_depr = s.Goptions.opt_depr;
Interface.opt_name = s.Goptions.opt_name;
Interface.opt_value = export_option_value s.Goptions.opt_value;
@@ -347,7 +340,7 @@ let set_options options =
| IntValue i -> Goptions.set_int_option_value name i
| StringValue s -> Goptions.set_string_option_value name s
| StringOptValue (Some s) -> Goptions.set_string_option_value name s
- | StringOptValue None -> Goptions.unset_option_value_gen None name
+ | StringOptValue None -> Goptions.unset_option_value_gen name
in
List.iter iter options
@@ -359,18 +352,15 @@ let about () = {
}
let handle_exn (e, info) =
+ let (e, info) = ExplainErr.process_vernac_interp_error (e, info) in
let dummy = Stateid.dummy in
let loc_of e = match Loc.get_loc e with
- | Some loc when not (Loc.is_ghost loc) -> Some (Loc.unloc loc)
- | _ -> None in
- let mk_msg () =
- let msg = read_stdout () in
- let msg = str msg ++ fnl () ++ CErrors.print ~info e in
- Richpp.richpp_of_pp msg
- in
+ | Some loc -> Some (Loc.unloc loc)
+ | _ -> None in
+ let mk_msg () = CErrors.print ~info e in
match e with
- | CErrors.Drop -> dummy, None, Richpp.richpp_of_string "Drop is not allowed by coqide!"
- | CErrors.Quit -> dummy, None, Richpp.richpp_of_string "Quit is not allowed by coqide!"
+ | CErrors.Drop -> dummy, None, Pp.str "Drop is not allowed by coqide!"
+ | CErrors.Quit -> dummy, None, Pp.str "Quit is not allowed by coqide!"
| e ->
match Stateid.get info with
| Some (valid, _) -> valid, loc_of info, mk_msg ()
@@ -379,35 +369,24 @@ let handle_exn (e, info) =
let init =
let initialized = ref false in
fun file ->
- if !initialized then anomaly (str "Already initialized")
+ if !initialized then anomaly (str "Already initialized.")
else begin
+ let init_sid = Stm.get_current_state ~doc:(get_doc ()) in
initialized := true;
match file with
- | None -> Stm.get_current_state ()
+ | None -> init_sid
| Some file ->
- let dir = Filename.dirname file in
- let open Loadpath in let open CUnix in
- let initial_id, _ =
- if not (is_in_load_paths (physical_path_of_string dir)) then
- Stm.add false ~ontop:(Stm.get_current_state ())
- 0 (Printf.sprintf "Add LoadPath \"%s\". " dir)
- else Stm.get_current_state (), `NewTip in
- Stm.set_compilation_hints file;
- Stm.finish ();
+ let doc, initial_id, _ =
+ get_doc (), init_sid, `NewTip in
+ if Filename.check_suffix file ".v" then
+ Stm.set_compilation_hints file;
+ set_doc (Stm.finish ~doc);
initial_id
end
-(* Retrocompatibility stuff *)
+(* Retrocompatibility stuff, disabled since 8.7 *)
let interp ((_raw, verbose), s) =
- let vernac_parse s =
- let pa = Pcoq.Gram.parsable (Stream.of_string s) in
- Flags.with_option Flags.we_are_parsing (fun () ->
- match Pcoq.Gram.entry_parse Pcoq.main_entry pa with
- | None -> raise (Invalid_argument "vernac_parse")
- | Some ast -> ast)
- () in
- Stm.interp verbose (vernac_parse s);
- Stm.get_current_state (), CSig.Inl (read_stdout ())
+ Stateid.dummy, CSig.Inr "The interp call has been disabled, please use Add."
(** When receiving the Quit call, we don't directly do an [exit 0],
but rather set this reference, in order to send a final answer
@@ -415,25 +394,17 @@ let interp ((_raw, verbose), s) =
let quit = ref false
-(** Serializes the output of Stm.get_ast *)
-let print_ast id =
- match Stm.get_ast id with
- | Some (expr, loc) -> begin
- try Texmacspp.tmpp expr loc
- with e -> Xml_datatype.PCData ("ERROR " ^ Printexc.to_string e)
- end
- | None -> Xml_datatype.PCData "ERROR"
+(** Disabled *)
+let print_ast id = Xml_datatype.PCData "ERROR"
(** Grouping all call handlers together + error handling *)
-let eval_call xml_oc log c =
+let eval_call c =
let interruptible f x =
catch_break := true;
Control.check_for_interrupt ();
let r = f x in
catch_break := false;
- let out = read_stdout () in
- if not (String.is_empty out) then log (str out);
r
in
let handler = {
@@ -451,6 +422,7 @@ let eval_call xml_oc log c =
Interface.quit = (fun () -> quit := true);
Interface.init = interruptible init;
Interface.about = interruptible about;
+ Interface.wait = interruptible wait;
Interface.interp = interruptible interp;
Interface.handle_exn = handle_exn;
Interface.stop_worker = Stm.stop_worker;
@@ -471,16 +443,8 @@ let print_xml =
try Xml_printer.print oc xml; Mutex.unlock m
with e -> let e = CErrors.push e in Mutex.unlock m; iraise e
-
-let slave_logger xml_oc ?loc level message =
- (* convert the message into XML *)
- let msg = hov 0 message in
- let () = pr_debug (Printf.sprintf "-> %S" (string_of_ppcmds msg)) in
- let xml = Xmlprotocol.of_message level loc (Richpp.richpp_of_pp message) in
- print_xml xml_oc xml
-
-let slave_feeder xml_oc msg =
- let xml = Xmlprotocol.of_feedback msg in
+let slave_feeder fmt xml_oc msg =
+ let xml = Xmlprotocol.(of_feedback fmt msg) in
print_xml xml_oc xml
(** The main loop *)
@@ -489,30 +453,36 @@ let slave_feeder xml_oc msg =
messages by [handle_exn] above. Otherwise, we die badly, without
trying to answer malformed requests. *)
-let loop () =
+let msg_format = ref (fun () ->
+ let margin = Option.default 72 (Topfmt.get_margin ()) in
+ Xmlprotocol.Richpp margin
+ )
+
+(* The loop ignores the command line arguments as the current model delegates
+ its handing to the toplevel container. *)
+let loop _args ~state =
+ let open Vernac.State in
+ set_doc state.doc;
init_signal_handler ();
catch_break := false;
- let in_ch, out_ch = Spawned.get_channels () in
- let xml_oc = Xml_printer.make (Xml_printer.TChannel out_ch) in
- let in_lb = Lexing.from_function (fun s len ->
- CThread.thread_friendly_read in_ch s ~off:0 ~len) in
- let xml_ic = Xml_parser.make (Xml_parser.SLexbuf in_lb) in
+ let in_ch, out_ch = Spawned.get_channels () in
+ let xml_oc = Xml_printer.make (Xml_printer.TChannel out_ch) in
+ let in_lb = Lexing.from_function (fun s len ->
+ CThread.thread_friendly_read in_ch s ~off:0 ~len) in
+ (* SEXP parser make *)
+ let xml_ic = Xml_parser.make (Xml_parser.SLexbuf in_lb) in
let () = Xml_parser.check_eof xml_ic false in
- Feedback.set_logger (slave_logger xml_oc);
- Feedback.add_feeder (slave_feeder xml_oc);
- (* We'll handle goal fetching and display in our own way *)
- Vernacentries.enable_goal_printing := false;
- Vernacentries.qed_display_script := false;
+ ignore (Feedback.add_feeder (slave_feeder (!msg_format ()) xml_oc));
while not !quit do
try
let xml_query = Xml_parser.parse xml_ic in
(* pr_with_pid (Xml_printer.to_string_fmt xml_query); *)
let Xmlprotocol.Unknown q = Xmlprotocol.to_call xml_query in
let () = pr_debug_call q in
- let r = eval_call xml_oc (slave_logger xml_oc Feedback.Notice) q in
+ let r = eval_call q in
let () = pr_debug_answer q r in
(* pr_with_pid (Xml_printer.to_string_fmt (Xmlprotocol.of_answer q r)); *)
- print_xml xml_oc (Xmlprotocol.of_answer q r);
+ print_xml xml_oc Xmlprotocol.(of_answer (!msg_format ()) q r);
flush out_ch
with
| Xml_parser.Error (Xml_parser.Empty, _) ->
@@ -534,16 +504,19 @@ let loop () =
let rec parse = function
| "--help-XML-protocol" :: rest ->
Xmlprotocol.document Xml_printer.to_string_fmt; exit 0
+ | "--xml_format=Ppcmds" :: rest ->
+ msg_format := (fun () -> Xmlprotocol.Ppcmds); parse rest
| x :: rest -> x :: parse rest
| [] -> []
-let () = Coqtop.toploop_init := (fun args ->
- let args = parse args in
- Flags.make_silent true;
- init_stdout ();
- CoqworkmgrApi.(init Flags.High);
+let () = Coqtop.toploop_init := (fun coq_args extra_args ->
+ let args = parse extra_args in
+ Flags.quiet := true;
+ CoqworkmgrApi.(init High);
args)
let () = Coqtop.toploop_run := loop
-let () = Usage.add_to_usage "coqidetop" " --help-XML-protocol print the documentation of the XML protocol used by CoqIDE\n"
+let () = Usage.add_to_usage "coqidetop"
+" --xml_format=Ppcmds serialize pretty printing messages using the std_ppcmds format\
+\n --help-XML-protocol print documentation of the Coq XML protocol\n"