package coq-serapi

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file sertop_sexp.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
(************************************************************************)
(*         *   The Coq Proof Assistant / The Coq Development Team       *)
(*  v      *         Copyright INRIA, CNRS and contributors             *)
(* <O___,, * (see version control and CREDITS file for authors & dates) *)
(*   VV/  **************************************************************)
(*    //   *    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)         *)
(************************************************************************)

(************************************************************************)
(* SerAPI: Coq interaction protocol with bidirectional serialization    *)
(************************************************************************)
(* Copyright 2016-2019 MINES ParisTech -- License LGPL 2.1+             *)
(* Copyright 2019-2023 Inria           -- License LGPL 2.1+             *)
(* Written by: Emilio J. Gallego Arias and others                       *)
(************************************************************************)

open Sexplib
open Sexplib.Conv

module SP = Serapi.Serapi_protocol

(******************************************************************************)
(* Global Protocol Options                                                    *)
(******************************************************************************)

type ser_opts =
{ in_chan  : in_channel          (* Input/Output channels                      *)
; out_chan : out_channel
                                 (* Printers                                   *)
; printer  : Sertop_ser.ser_printer

; debug    : bool
; set_impredicative_set : bool
; allow_sprop : bool
; indices_matter : bool
; print0   : bool
; lheader  : bool                (* Print lenght header (deprecated)           *)

  (* Coq options *)
; no_init  : bool                (* Whether to create the initial document     *)
; no_prelude : bool              (* Whether to load stdlib's prelude           *)
; topfile  : string option       (* Top name is derived from topfile name      *)
; ml_path : string list
; vo_path : Loadpath.vo_path list (** From -R and -Q options usually           *)
; async    : Sertop_init.async_flags
}

(******************************************************************************)
(* Input/Output                                                               *)
(******************************************************************************)
(*                                                                            *)
(* Until this point, we've been independent of a particular format or         *)
(* or serialization, with all the operations defined at the symbolic level.   *)
(*                                                                            *)
(* It is now time to unfortunately abandon our fairy-tale and face the real,  *)
(* ugly world in these last 40 lines!                                         *)
(*                                                                            *)
(******************************************************************************)

type sertop_cmd =
  | SerApi of Sertop_ser.tagged_cmd
  (** Regular   *)
  | Fork of { fifo_in : string; fifo_out : string }
  (** Fork SerAPI to a new process, useful for people doing search
     [but heavy], the forked process will create and use the provided
     FIFO's for input / output *)
  | Quit
  (** Exit sertop *)
  [@@deriving sexp]

let is_cmd_quit cmd = match cmd with
  | Quit -> true
  | _    -> false

(* Loop state *)
module Ctx = struct

  type t =
    { in_chan : Stdlib.in_channel
    ; out_chan : Stdlib.out_channel
    ; out_fmt : Format.formatter
    ; cmd_id : int
    ; st : SP.State.t
    }

  let make ?in_file ?ldir ~cmd_id ~in_chan ~out_chan () =
    let out_fmt = Format.formatter_of_out_channel out_chan in
    let st = SP.State.make ?in_file ?ldir () in
    { out_chan; out_fmt; in_chan; cmd_id; st }

end

(* XXX: Improve by using manual tag parsing. *)
let read_cmd ~(ctx : Ctx.t) ~pp_err =
  let rec read_loop () =
    try
      let cmd_sexp = Sexp.input_sexp ctx.in_chan in
      begin
        try sertop_cmd_of_sexp cmd_sexp
        with
        | _exn ->
          begin
            try SerApi (Sertop_ser.tagged_cmd_of_sexp cmd_sexp)
            with
            | _exn ->
              SerApi (string_of_int ctx.cmd_id, Sertop_ser.cmd_of_sexp cmd_sexp)
          end
      end
    with
    | End_of_file   ->
      Quit
    | exn           ->
      pp_err ctx.out_fmt (sexp_of_exn exn);
      (read_loop [@ocaml.tailcall]) ()
  in read_loop ()

let out_sexp opts =
  let open Format                                                               in

  let pp_sexp = Sertop_ser.select_printer opts.printer in

  let pp_term = if opts.print0 then fun fmt () -> fprintf fmt "%c" (Char.chr 0)
                               else fun fmt () -> fprintf fmt "@\n"             in
  if opts.lheader then
    fun fmt a ->
      fprintf str_formatter "@[%a@]%a%!" pp_sexp a pp_term ();
      let out = flush_str_formatter () in
      fprintf fmt "@[byte-length: %d@\n%s@]%!" (String.length out) out
  else
    fun fmt a -> fprintf fmt "@[%a@]%a%!" pp_sexp a pp_term ()

(** We could use Sexp.to_string too  *)
let out_answer opts =
  let pp_sexp = out_sexp opts in
  fun fmt a -> pp_sexp fmt (Sertop_ser.sexp_of_answer a)

(** Set the topname from optional topfile string or default if None **)
let doc_type topfile =
  match topfile with
  | None ->
    let sertop_dp = Names.(DirPath.make [Id.of_string "SerTop"]) in
    Stm.Interactive (TopLogical sertop_dp)
  | Some filename -> Stm.Interactive (Coqargs.TopPhysical filename)

let process_serloop_cmd ~(ctx : Ctx.t) ~pp_ack ~pp_answer ~pp_err ~pp_feed (cmd : sertop_cmd) : Ctx.t =
  let out = ctx.out_fmt in
  (* Collect terminated children *)
  let () =
    try
      let _pid, _status = Unix.waitpid [Unix.WNOHANG] (-1) in
      ()
    with
      Unix.Unix_error(Unix.ECHILD, _, _) ->
      (* No children for now *)
      ()
  in
  match cmd with
  | SerApi (cmd_tag, cmd) ->
    pp_ack out cmd_tag;
    let ans, st = SP.exec_cmd ctx.st cmd in
    List.iter (pp_answer out) @@ List.map (fun a -> SP.Answer (cmd_tag, a)) ans;
    { ctx with st }
  | Fork { fifo_in ; fifo_out } ->
    let pid = Unix.fork () in
    if pid = 0 then begin
      (* Children: close previous channels *)
      Stdlib.close_in ctx.in_chan;
      Format.pp_print_flush ctx.out_fmt ();
      Stdlib.close_out ctx.out_chan;
      (* Create new ones *)
      let () = Unix.mkfifo fifo_in 0o640 in
      let in_chan = Stdlib.open_in fifo_in in
      let () = Unix.mkfifo fifo_out 0o640 in
      let out_chan = Stdlib.open_out fifo_out in
      let out_fmt = Format.formatter_of_out_channel out_chan in
      Sertop_init.update_fb_handler ~pp_feed out_fmt;
      { ctx with in_chan; out_chan; out_fmt }
    end
    else
      (* Parent *)
      let () = pp_err out Sexp.(List [Atom "Forked"; Atom (string_of_int pid)]) in
      ctx
  | Quit ->
    ctx

let ser_loop ser_opts =

  (* Create closures for printers given initial options *)
  let pp_answer = out_answer ser_opts in
  let pp_err    = out_sexp ser_opts   in

  (* XXX EG: I don't understand this well, why is this lock needed ??
     Review fork code in CoqworkmgrApi *)
  let pr_mutex     = Mutex.create ()                                   in
  let ser_lock f x = Mutex.lock   pr_mutex;
                     f x;
                     Mutex.unlock pr_mutex                             in

  (* Wrap printers  *)
  let pp_answer out  = ser_lock (pp_answer out) in
  let pp_err    out  = ser_lock (pp_err out)    in
  let pp_ack out cid = pp_answer out (SP.Answer (cid, SP.Ack)) in
  let pp_opt fb = Sertop_util.feedback_opt_filter fb           in
  let pp_feed out fb =
    Option.iter (fun fb -> pp_answer out (SP.Feedback (Sertop_util.feedback_tr fb))) (pp_opt fb) in

  let ldir = Option.map Serapi.Serapi_paths.dirpath_of_file ser_opts.topfile in
  let ctx = Ctx.make
      ?in_file:ser_opts.topfile ?ldir ~cmd_id:0 ~in_chan:ser_opts.in_chan ~out_chan:ser_opts.out_chan () in

  (* Init Coq *)
  let ml_path, vo_path = ser_opts.ml_path, ser_opts.vo_path in

  let () = Sertop_init.(
      coq_init
        { fb_handler   = pp_feed
        ; plugin_load  = None
        ; debug        = ser_opts.debug
        ; set_impredicative_set  = ser_opts.set_impredicative_set
        ; allow_sprop  = ser_opts.allow_sprop
        ; indices_matter = ser_opts.indices_matter
        ; ml_path
        ; vo_path
        }) ctx.out_fmt
  in

  (* Follow the same approach than coqtop for now: allow Coq to be
   * interrupted by Ctrl-C. Not entirely safe or race free... but we
   * trust the IDEs to send the signal on coherent IO state.
   *)
  Sys.catch_break true;

  let injections =
    if ser_opts.no_prelude then []
    else [Coqargs.RequireInjection {lib="Coq.Init.Prelude"; prefix=None; export=Some Lib.Import;}] in

  let stm_options = Sertop_init.process_stm_flags ser_opts.async in
  Stm.init_process stm_options;

  if not ser_opts.no_init then begin
    let ndoc = { Stm.doc_type = doc_type ser_opts.topfile
               ; injections
               } in
    let _ = Stm.new_doc ndoc in
    ()
  end;

  let incr_cmdid (ctx : Ctx.t) =
    { ctx with cmd_id = ctx.cmd_id + 1 } in

  (* Main loop *)
  let rec loop ctx =
    let quit, ctx =
      try
        let scmd = read_cmd ~ctx ~pp_err in
        ( is_cmd_quit scmd
        , process_serloop_cmd ~ctx ~pp_ack ~pp_answer ~pp_err ~pp_feed scmd)
      with Sys.Break -> false, ctx
    in
    if quit then () else (loop [@ocaml.tailcall]) (incr_cmdid ctx)
  in loop ctx
OCaml

Innovation. Community. Security.