aboutsummaryrefslogtreecommitdiffhomepage
path: root/toplevel/protectedtoplevel.ml
blob: cc128471884ef7cbc2987204af86df13b4cc770a (plain)
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

(* $Id$ *)

open Pp
open Line_oriented_parser
open Vernac

(* The toplevel parsing loop we propose here is more robust to printing
   errors.  The philosophy is that all commands should be individually wrapped
   in predefined markers.  If there is a parsing error, everything down to
   the closing marker will be discarded.  Also there is always an aknowledge
   message associated to a wrapped command. *)


(* It is also possible to have break signals sent by other programs.  However,
   there are some operations that should not be interrupted, especially, those
   operations that are outputing data.
*)

let break_happened = ref false

let output_results_nl stream =
  let _ = 
    Sys.signal Sys.sigint
      (Sys.Signal_handle (fun _ -> break_happened := true;()))
  in
  mSGNL stream

let rearm_break () =
  let _ = 
    Sys.signal Sys.sigint (Sys.Signal_handle(fun _ -> raise Sys.Break)) in
  ()

let check_break () =
  if !break_happened then begin
    break_happened := false;
    raise Sys.Break
  end

(* All commands are acknowledged. *)

let global_request_id = ref 013

let acknowledge_command request_id =
  [< 'fNL; 'sTR "message"; 'fNL; 'sTR "acknowledge"; 'fNL; 
     'iNT request_id; 'fNL; 'sTR "***"; 'fNL >]

(* The markers are chosen to be likely to be different from any
  existing text. *)

let start_marker = ref "protected_loop_start_command"
let end_marker = ref "protected_loop_end_command"
let start_length = ref (String.length !start_marker)
let start_marker_buffer = ref (String.make !start_length ' ')

let set_start_marker s =
  start_marker := s;
  start_length := String.length s;
  start_marker_buffer := String.make !start_length ' '
    
let set_end_marker s =
  end_marker := s

let rec parse_one_command input_channel =
  let this_line = input_line input_channel in
  if ((String.length this_line) >= !start_length) then begin
    String.blit this_line 0 !start_marker_buffer 0 !start_length;
    if !start_marker_buffer = !start_marker then
      let snd_line = input_line input_channel in
      begin 
	try
          global_request_id := int_of_string snd_line
	with
          | e -> failwith ("could not parse the request identifier |"^
                           snd_line ^ "|") 
      end;
      let stream_tail =
        Stream.from
          (line_oriented_channel_to_option !end_marker input_channel) in
      begin
	try
	  check_break();
          rearm_break();
          raw_do_vernac (Pcoq.Gram.parsable stream_tail);
          output_results_nl(acknowledge_command !global_request_id);
          rearm_break();
        with
          | e -> begin flush_until_end_of_stream stream_tail; raise e end
      end;
      flush_until_end_of_stream stream_tail
    else
      parse_one_command input_channel
  end else
    parse_one_command input_channel

let protected_loop input_chan =
  let rec explain_and_restart e =
    output_results_nl(Errors.explain_exn e);
    rearm_break();
    looprec input_chan;
  and looprec input_chan =
    try 
      while true do parse_one_command input_chan done
    with
      | Vernacinterp.Drop -> raise Vernacinterp.Drop
      | Vernacinterp.Quit -> exit 0
      | End_of_file -> exit 0
      | DuringCommandInterp(loc, Vernacinterp.Quit) -> raise Vernacinterp.Quit
      | DuringCommandInterp(loc, Vernacinterp.Drop) -> raise Vernacinterp.Drop
      | DuringCommandInterp(loc, e) ->
	  explain_and_restart e
      | e -> explain_and_restart e 
  in
  mSGNL [<'sTR "Starting Centaur Specialized loop" >];
  looprec input_chan