Coq's new(ish) IDE protocol [draft]

Coq is a proof assistant and programming language with dependent types, my favourite thing in all the world. One of the programs in the distribution, named CoqIDE, is an editor which speaks to the proof checker (called the “toplevel”, since Coq was written by OCaml people) using an XML protocol. Other people wrote editor plugins, such as the Vim plugin Coquille, which used this protocol and allowed you to write Coq without having to deal with CoqIDE’s idiosyncrasies. In August of 2012, version 8.4 came out, and it had a new protocol. However, it still supported the old one, and coquille still worked just fine.

At the beginning of 2016, version 8.5 was released. It did remove the old protocol, meaning that programs which hadn’t switched to the new one broke. Coquille has switched to the new protocol, but at the time of writing this it’s still not totally working. Which has led me to reading the documentation (and source code) for the new protocol so I can do something about it.

This post is a work in progress, while I continue to pore over the source and experiment with coqtop . My findings are going towards a Haskell library. And when that’s working, a plugin for NeoVim.

Overview

The first thing you need to do is to start coqtop with the right arguments to use this protocol, namely:

coqtop -toploop coqidetop -main-channel stdfds

Setting -main-channel stdfds means using stdin / stdout to communicate. You can also use a socket, if you prefer. You can tell coqtop to read from port 12345 and write to localhost port 54321 by writing

coqtop -toploop coqidetop -main-channel localhost:12345:54321

To make a call to coqtop , you wrap the arguments (which I’ll describe below) in a call element. The call name itself is put in the val attribute. For example, to quit coqtop , I would send a call message, which takes a unit argument.

<call val= "Quit" > <unit/> </call>

I’d get a response from coqtop , a value element with a val of either "good" for a success (also a unit)…

<value val= "good" > <unit/> </value>

…or "fail" for a failure. (Though I don’t think Quit ever fails.)

<value val= "fail" > <state_id val= "123" /> (error message here with spaces replaced with ) </value>

Coq doesn’t put line breaks after each response, or in fact anywhere except (for some reason) before unwrapped text fragments like the error message in a failure value . Text fragments also have all of their space characters replaced with .

State IDs

In a failure response, as well as successes for state-changing commands, you get a state_id that refers to the prover’s new state. It’s your responsibility to keep track of what states these refer to.

Feedback messages

TODO: Here’s the datatypes from lib/feedback.mli . I’m still not sure about these, other than what is obvious from the names and comments.

type feedback_content = (* STM mandatory data (must be displayed) *) | Processed | Incomplete | Complete | ErrorMsg of Loc.t * string (* STM optional data *) | ProcessingIn of string | InProgress of int | WorkerStatus of string * string (* Generally useful metadata *) | Goals of Loc.t * string | AddedAxiom | GlobRef of Loc.t * string * string * string * string | GlobDef of Loc.t * string * string * string | FileDependency of string option * string | FileLoaded of string * string (* Extra metadata *) | Custom of Loc.t * string * xml (* Old generic messages *) | Message of message type feedback = { id : edit_or_state_id; (* The document part concerned *) contents : feedback_content; (* The payload *) route : route_id; (* Extra routing info *) }

Processed Sent when a fragment (indicated by id ) has finished processing. ProcessingIn of string Sent when a fragment starts processing. The string is the name of the worker thread it’s running on. FileDependency of string option * string Sent when a file dependency (e.g. Require ) is encountered. The first field is None for a direct dependency, and Some f otherwise, where f is the file the dependency occurred in. The second field is the filename of the field being depended on. FileLoaded of string * string Sent when a file dependency has finished loading. The first field is the module name, and the second is the filename.

Commands

The commands that the protocol understands are:

Init

Initialise the environment and (optionally) load a file

You call this once only, at startup. You can’t use it to clear the environment for a new session; presumably you just quit & restart to do that.

Input ( string option ): Some (filename) for an existing file None for an empty buffer

): Output ( state_id ): State ID referring to the start of the file

<call val= "Init" > <option val= "none" /> </call> <value val= "good" > <state_id val= "1" /> </value>

But, if you try to do it again in the same session:

<call val= "Init" > <option val= "none" /> </call> <value val= "fail" > <state_id val= "0" /> Anomaly: Already initialized. Please report. </value>

About

Return some information about coqtop

Input ( unit )

) Output ( coq_info ): Coq version, protocol version, release date, and compilation time

<call val= "About" > <unit/> </call> <value val= "good" > <coq_info> <string> 8.5 </string> <string> 20140312 </string> <string> June 2016 </string> <string> Jun 9 2016 12:4:46 </string> </coq_info> </value>

Status

Ask about the current state of coqtop

Input ( bool ): whether to force evaluation of all pending statements

): whether to force evaluation of all pending statements Output ( status ): Current module path ( string list ), current proof name ( string option ), pending proof obligations ( string list , in an unspecified order), and an identifier for the current proof’s state ( int ) (TODO: ??)

Calling Status is also when you find out about incorrect proofs.

<call val= "Status" > <bool val= "false" /> </call> <feedback object= "state" route= "0" > <state_id val= "2" /> <feedback_content val= "processingin" > <string> master </string> </feedback_content> </feedback> <feedback object= "state" route= "0" > <state_id val= "1" /> <feedback_content val= "processed" /> </feedback> <feedback object= "state" route= "0" > <state_id val= "2" /> <feedback_content val= "processed" /> </feedback> <value val= "good" > <status> <list><string> Top </string></list> <option val= "some" > <string> Unnamed_thm </string> </option> <list><string> Unnamed_thm </string></list> <int> 0 </int> </status> </value>

Add

Advance the cursor.

Input ( (string * int) * (state_id * bool) ): The phrase to pass to Coq; An ‘edit ID’, which any feedback will contain in an edit_id element; The ID of the state that you’re expecting Coq to be in; Whether to be verbose.

): Output ( state_id * ((unit, state_id) union * string) ): The state ID of the entered phrase; The current state ID, if it’s different (which it might be if, e.g., the phrase ended a module); Some output.

):

<call val= "Add" > <pair> <pair> <string> Goal 1 > 0. </string> <int> 2 </int> </pair> <pair> <state_id val= "1" /> <bool val= "true" /> </pair> </pair> </call> <value val= "good" > <pair> <state_id val= "2" /> <pair> <union val= "in_l" > <unit/> </union> <string></string> </pair> </pair> </value>

<call val= "Add" > <pair> <!-- no full stop at the end --> <pair> <string> Goal 1 > 0 </string> <int> 2 </int> </pair> <pair> <state_id val= "1" /> <bool val= "true" /> </pair> </pair> </call> <feedback object= "edit" route= "0" > <edit_id val= "2" /> <feedback_content val= "errormsg" > <loc start= "10" stop= "11" /><string> Syntax error: ' . ' expected after [vernac:command] (in [vernac_aux]). </string> </feedback_content> </feedback> <value val= "fail" loc_s= "10" loc_e= "11" ><state_id val= "0" /> Syntax error: ' . ' expected after [vernac:command] (in [vernac_aux]). </value>

Edit_at

(not EditAt )

Rewind Coq’s state to a previous state i. All IDs after i expire when you do, so Edit_at can only jump backwards. Attempting to jump to an expired ID raises a Stm.Vcs_aux.Expired exception.

Input ( state_id ): the state to jump back to.

): the state to jump back to. Output ( (unit, state_id * (state_id * state_id)) union ): If the document is rewound to the state given, then Inl () ; If the given state is in a zone that can be focused (TODO ???), then Inr (start, (stop, tip)) , where start and stop delimit the zone, and tip is the new current ID.

):

TODO examples

Query

Executes a query command like Check . The document state is not updated so the response message is the only thing of interest.

Input ( string * state_id ): the query and the expected current state ID

): the query and the expected current state ID Output ( string ): the response to the query

TODO examples

Goal

If a proof is in progress, get the current proof state.

Input is unit

Output ( goals option ): None if no proof is in progress; If there is a proof in progress, then: The focused goals ( goal list ); The unfocused goals ( (goal list * goal list) list ); Shelved goals ( goal list ); Given up on goals ( goal list ). A goal is: An ID ( string ); A list of hypotheses ( richpp list ); A conclusion ( richpp ).

):

See below for richpp .

TODO examples

Evars

If a proof is in progress, get a list of uninstantiated evars (metavariables).

Input is unit

Output ( evar list option ): None if there is no proof in progress, otherwise a list of evar elements containing names and types as unstructured strings.

<!-- Goal exists x, x = 0. eexists. --> <call val= "Evars" > <unit/> </call> <!-- preceded by lots of <feedback> messages, probably --> <value val= "good" > <option val= "some" > <list> <evar> ?x : [ |- nat] </evar> <evar> ?Goal : [ |- ?x = 0] </evar> </list> </option> </value>

Hints

Get a list of tactics which might be applicable to the current goal.

Input is unit

Output ( (hint list * hint) option ): None if no proof is in progress. A hint is a list of pairs of strings, where each pair (lbl, tac) is a display form and a form suitable for Add .

NB: You should probably call Status before this to ensure that Coq is in the proof state. Otherwise you might get a spurious None .

<!-- Goal False -> True. --> <call val= "Hints" > <unit/> </call> <value val= "good" > <option val= "some" > <pair> <list/> <list> <pair> <string> intro </string><string> intro. </string> </pair> <pair> <string> intros </string><string> intros. </string> </pair> <pair> <string> intuition </string><string> intuition. </string> </pair> <pair> <string> reflexivity </string><string> reflexivity. </string> </pair> <!-- many more... --> </list> </pair> </option> </value>

Search

Search for objects in the global and local context.

Input ( (search_constraint * bool) list ): A list of search criteria, negated if the accompanying bool is false . A search_constraint , whose element name is actually search_cst , is one of: name_pattern : Whether the name satisfies a regular expression. Takes a string. type_pattern : Whether the (whole) type matches some pattern, with either _ or evars as wildcards (e.g. "nat -> ?A" , or "nat -> _" ). Takes a string. subtype_pattern : Whether some subterm of the type matches a pattern. Takes a string in the same format as type_pattern . in_module : Restrict to contents of a module (or skip a module, if negated). Takes a list of strings, for the name’s components (e.g. ["Coq"; "Init"; "Peano"] ). include_blacklist : if true , ignore the search blacklist. Takes no argument.

): A list of search criteria, negated if the accompanying is . A , , is one of: Output ( string coq_object list ): A list of all results. An 'a coq_object is: A module prefix saying where the definition is found ( string list ); A shortest unambiguous name ( string list ), which may be qualified if two objects of the same name are in scope—the object’s fully qualified name is prefix @ name ; The object itself ( 'a )—in this case, its type, as a string .

): A list of all results. An is:

<!-- find anything with a nat anywhere in its type --> <call val= "Search" > <list> <pair> <search_cst val= "subtype_pattern" > <string> nat </string> </search_cst> <bool val= "true" /> </pair> </list> </call> <value val= "good" > <list> <coq_object> <list> <string> Coq </string><string> Init </string><string> Wf </string> </list> <list> <string> Acc_intro_generator </string> </list> <string> forall (A : Type) (R : A -> A -> Prop), nat -> well_founded R -> well_founded R </string> </coq_object> <coq_object> <list> <string> Coq </string><string> Init </string><string> Specif </string> </list> <list> <string> dependent_choice </string> </list> <string> forall (X : Set) (R : X -> X -> Prop), (forall x : X, {y : X | R x y}) -> forall x0 : X, {f : nat -> X | f 0 = x0 /\ (forall n : nat, R (f n) (f (S n)))} </string> </coq_object> <coq_object> <list> <string> Coq </string><string> Init </string><string> Peano </string> </list> <list> <string> nat_rect_plus </string> </list> <string> forall (n m : nat) (A : Type) (f : A -> A) (x : A), nat_rect (fun _ : nat => A) x (fun _ : nat => f) (n + m) = nat_rect (fun _ : nat => A) (nat_rect (fun _ : nat => A) x (fun _ : nat => f) m) (fun _ : nat => f) n </string> </coq_object> <!-- Many, many more... --> </list> </value>

GetOptions

Input is unit

Output ( (string list * option_state) list ): each element in the list is a pair of an option name (given as a list of words) and the option state, consisting of: Whether the option is synchronous ( bool ); Whether the option is deprecated ( bool ); A short description used in the output of Test ( string ); option_value ) boolvalue containing a bool ; intvalue containing an int option (not just an int ); stringvalue containing a string ; stringoptvalue containing a string option . ; one of: The current value (

): each element in the list is a pair of an option name (given as a list of words) and the option state, consisting of:

<call val= "GetOptions" > <unit /> </call> <value val= "good" > <list> <pair> <list> <string> Warnings </string> </list> <option_state> <bool val= "true" /><bool val= "false" /> <string> warnings display </string> <option_value val= "stringvalue" > <string></string> </option_value> </option_state> </pair> <pair> <list> <string> Universe </string><string> Polymorphism </string> </list> <option_state> <bool val= "true" /><bool val= "false" /> <string> universe polymorphism </string> <option_value val= "boolvalue" > <bool val= "false" /> </option_value> </option_state> </pair> <pair> <list> <string> Universe </string> <string> Minimization </string> <string> ToSet </string> </list> <option_state> <bool val= "true" /><bool val= "false" /> <string> minimization to Set </string> <option_value val= "boolvalue" > <bool val= "true" /> </option_value> </option_state> </pair> <!-- others ... --> </list> </value>

SetOptions

Set the values of some options. This isn’t atomic so if it fails it’s your responsibility to clean up the mess.

Input ( (string list * option_value) list ): option_value is the same as for the return value of GetOptions .

TODO: example

MkCases

Make a list of patterns for a datatype.

Input ( string ): the datatypes’ name.

): the datatypes’ name. Output ( string list list ): a list of patterns, each of which is a list of strings—a constructor name followed by a list of pattern variables. For example, for nat the output is [["O"]; ["S"; "x"]] .

<call val= "MkCases" > <string> list </string> </call> <value val= "good" > <list> <list> <string> nil </string> </list> <list> <!-- Coq isn't very good at picking names --> <string> cons </string> <string> x </string> <string> x0 </string> </list> </list> </value>

Quit

Quit Coq.

Input and output are both unit .

<call val= "Quit" > <unit/> </call> <value val= "good" > <unit/> </value> <!-- Coq quits -->

Formatted text

Formatted text is used for feedback messages and for syntax highlighting in some places such as Goal . Rich text is surrounded in both a richpp and a _ element. Each formatted section is identified by a tag whose name is a sequence of style names, separated by dots. For example, a hypothesis in the response to Goal might be written as: