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
open Sexplib
open Sexplib.Conv
module SP = Serapi.Serapi_protocol
type ser_opts =
{ in_chan : in_channel
; out_chan : out_channel
; printer : Sertop_ser.ser_printer
; debug : bool
; allow_sprop : bool
; indices_matter : bool
; print0 : bool
; lheader : bool
; no_init : bool
; no_prelude : bool
; topfile : string option
; ml_path : string list
; vo_path : Loadpath.vo_path list (** From -R and -Q options usually *)
; async : Sertop_init.async_flags
}
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
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
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
let () =
try
let _pid, _status = Unix.waitpid [Unix.WNOHANG] (-1) in
()
with
Unix.Unix_error(Unix.ECHILD, _, _) ->
()
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
Stdlib.close_in ctx.in_chan;
Format.pp_print_flush ctx.out_fmt ();
Stdlib.close_out ctx.out_chan;
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
let () = pp_err out Sexp.(List [Atom "Forked"; Atom (string_of_int pid)]) in
ctx
| Quit ->
ctx
let ser_loop ser_opts =
let pp_answer = out_answer ser_opts in
let pp_err = out_sexp ser_opts in
let pr_mutex = Mutex.create () in
let ser_lock f x = Mutex.lock pr_mutex;
f x;
Mutex.unlock pr_mutex in
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
let ml_path, vo_path = ser_opts.ml_path, ser_opts.vo_path in
let () = Sertop_init.(
coq_init
{ fb_handler = pp_feed
; ml_load = None
; debug = ser_opts.debug
; allow_sprop = ser_opts.allow_sprop
; indices_matter = ser_opts.indices_matter
; ml_path
; vo_path
}) ctx.out_fmt
in
Sys.catch_break true;
let injections =
if ser_opts.no_prelude then []
else [Coqargs.RequireInjection ("Coq.Init.Prelude", None, Some false)] in
let stm_options = Sertop_init.process_stm_flags ser_opts.async in
if not ser_opts.no_init then begin
let ndoc = { Stm.doc_type = doc_type ser_opts.topfile
; injections
; stm_options
} in
let _ = Stm.new_doc ndoc in
()
end;
let incr_cmdid (ctx : Ctx.t) =
{ ctx with cmd_id = ctx.cmd_id + 1 } in
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