1 (**************************************************************************)
2 (* *)
3 (* OCaml *)
4 (* *)
5 (* Daniel de Rauglaudre, projet Cristal, INRIA Rocquencourt *)
6 (* *)
7 (* Copyright 2002 Institut National de Recherche en Informatique et *)
8 (* en Automatique. *)
9 (* *)
10 (* All rights reserved. This file is distributed under the terms of *)
11 (* the GNU Lesser General Public License version 2.1, with the *)
12 (* special exception on linking described in the file LICENSE. *)
13 (* *)
14 (**************************************************************************)
15
16 open Format
17
18 type error =
19 | CannotRun of string
20 | WrongMagic of string
21
22 exception Error of error
23
24 (* Optionally preprocess a source file *)
25
26 let call_external_preprocessor sourcefile pp =
27 let tmpfile = Filename.temp_file "ocamlpp" "" in
28 let comm = Printf.sprintf "%s %s > %s"
29 pp (Filename.quote sourcefile) tmpfile
30 in
31 if Ccomp.command comm <> 0 then begin
32 Misc.remove_file tmpfile;
33 raise (Error (CannotRun comm));
34 end;
35 tmpfile
36
37 let preprocess sourcefile =
38 match !Clflags.preprocessor with
39 None -> sourcefile
40 | Some pp ->
41 Profile.record "-pp"
42 (call_external_preprocessor sourcefile) pp
43
44
45 let remove_preprocessed inputfile =
46 match !Clflags.preprocessor with
47 None -> ()
48 | Some _ -> Misc.remove_file inputfile
49
50 type 'a ast_kind =
51 | Structure : Parsetree.structure ast_kind
52 | Signature : Parsetree.signature ast_kind
53
54 let magic_of_kind : type a . a ast_kind -> string = function
55 | Structure -> Config.ast_impl_magic_number
56 | Signature -> Config.ast_intf_magic_number
57
58 (* Note: some of the functions here should go to Ast_mapper instead,
59 which would encapsulate the "binary AST" protocol. *)
60
61 let write_ast (type a) (kind : a ast_kind) fn (ast : a) =
62 let oc = open_out_bin fn in
63 output_string oc (magic_of_kind kind);
64 output_value oc (!Location.input_name : string);
65 output_value oc (ast : a);
66 close_out oc
67
68 let apply_rewriter kind fn_in ppx =
69 let magic = magic_of_kind kind in
70 let fn_out = Filename.temp_file "camlppx" "" in
71 let comm =
72 Printf.sprintf "%s %s %s" ppx (Filename.quote fn_in) (Filename.quote fn_out)
73 in
74 let ok = Ccomp.command comm = 0 in
75 Misc.remove_file fn_in;
76 if not ok then begin
77 Misc.remove_file fn_out;
78 raise (Error (CannotRun comm));
79 end;
80 if not (Sys.file_exists fn_out) then
81 raise (Error (WrongMagic comm));
82 (* check magic before passing to the next ppx *)
83 let ic = open_in_bin fn_out in
84 let buffer =
85 try really_input_string ic (String.length magic) with End_of_file -> "" in
86 close_in ic;
87 if buffer <> magic then begin
88 Misc.remove_file fn_out;
89 raise (Error (WrongMagic comm));
90 end;
91 fn_out
92
93 let read_ast (type a) (kind : a ast_kind) fn : a =
94 let ic = open_in_bin fn in
95 Misc.try_finally
96 ~always:(fun () -> close_in ic; Misc.remove_file fn)
97 (fun () ->
98 let magic = magic_of_kind kind in
99 let buffer = really_input_string ic (String.length magic) in
100 assert(buffer = magic); (* already checked by apply_rewriter *)
101 Location.input_name := (input_value ic : string);
102 (input_value ic : a)
103 )
104
105 let rewrite kind ppxs ast =
106 let fn = Filename.temp_file "camlppx" "" in
107 write_ast kind fn ast;
108 let fn = List.fold_left (apply_rewriter kind) fn (List.rev ppxs) in
109 read_ast kind fn
110
111 let apply_rewriters_str ?(restore = true) ~tool_name ast =
112 match !Clflags.all_ppx with
113 | [] -> ast
114 | ppxs ->
115 let ast =
116 ast
117 |> Ast_mapper.add_ppx_context_str ~tool_name
118 |> rewrite Structure ppxs
119 |> Ast_mapper.drop_ppx_context_str ~restore
120 in
121 Ast_invariants.structure ast; ast
122
123 let apply_rewriters_sig ?(restore = true) ~tool_name ast =
124 match !Clflags.all_ppx with
125 | [] -> ast
126 | ppxs ->
127 let ast =
128 ast
129 |> Ast_mapper.add_ppx_context_sig ~tool_name
130 |> rewrite Signature ppxs
131 |> Ast_mapper.drop_ppx_context_sig ~restore
132 in
133 Ast_invariants.signature ast; ast
134
135 let apply_rewriters ?restore ~tool_name
136 (type a) (kind : a ast_kind) (ast : a) : a =
137 match kind with
138 | Structure ->
139 apply_rewriters_str ?restore ~tool_name ast
140 | Signature ->
141 apply_rewriters_sig ?restore ~tool_name ast
142
143 (* Parse a file or get a dumped syntax tree from it *)
144
145 exception Outdated_version
146
147 let open_and_check_magic inputfile ast_magic =
148 let ic = open_in_bin inputfile in
149 let is_ast_file =
150 try
151 let buffer = really_input_string ic (String.length ast_magic) in
152 if buffer = ast_magic then true
153 else if String.sub buffer 0 9 = String.sub ast_magic 0 9 then
154 raise Outdated_version
155 else false
156 with
157 Outdated_version ->
158 Misc.fatal_error "OCaml and preprocessor have incompatible versions"
159 | _ -> false
160 in
161 (ic, is_ast_file)
162
163 let parse (type a) (kind : a ast_kind) lexbuf : a =
164 match kind with
165 | Structure -> Parse.implementation lexbuf
166 | Signature -> Parse.interface lexbuf
167
168 let file_aux ~tool_name inputfile (type a) parse_fun invariant_fun
169 (kind : a ast_kind) : a =
170 let ast_magic = magic_of_kind kind in
171 let (ic, is_ast_file) = open_and_check_magic inputfile ast_magic in
172 let ast =
173 try
174 if is_ast_file then begin
175 Location.input_name := (input_value ic : string);
176 if !Clflags.unsafe then
177 Location.prerr_warning (Location.in_file !Location.input_name)
178 Warnings.Unsafe_without_parsing;
179 let ast = (input_value ic : a) in
180 if !Clflags.all_ppx = [] then invariant_fun ast;
181 (* if all_ppx <> [], invariant_fun will be called by apply_rewriters *)
182 ast
183 end else begin
184 seek_in ic 0;
185 let lexbuf = Lexing.from_channel ic in
186 Location.init lexbuf inputfile;
187 Location.input_lexbuf := Some lexbuf;
188 Profile.record_call "parser" (fun () -> parse_fun lexbuf)
189 end
190 with x -> close_in ic; raise x
191 in
192 close_in ic;
193 Profile.record_call "-ppx" (fun () ->
194 apply_rewriters ~restore:false ~tool_name kind ast
195 )
196
197 let file ~tool_name inputfile parse_fun ast_kind =
198 file_aux ~tool_name inputfile parse_fun ignore ast_kind
199
200 let report_error ppf = function
201 | CannotRun cmd ->
202 fprintf ppf "Error while running external preprocessor@.\
203 Command line: %s@." cmd
204 | WrongMagic cmd ->
205 fprintf ppf "External preprocessor does not produce a valid file@.\
206 Command line: %s@." cmd
207
208 let () =
209 Location.register_error_of_exn
210 (function
211 | Error err -> Some (Location.error_of_printer_file report_error err)
212 | _ -> None
213 )
214
215 let parse_file ~tool_name invariant_fun parse kind sourcefile =
216 Location.input_name := sourcefile;
217 let inputfile = preprocess sourcefile in
218 Misc.try_finally
219 (fun () ->
220 Profile.record_call "parsing" @@ fun () ->
221 file_aux ~tool_name inputfile parse invariant_fun kind)
222 ~always:(fun () -> remove_preprocessed inputfile)
223
224 let parse_implementation ~tool_name sourcefile =
225 parse_file ~tool_name Ast_invariants.structure
226 (parse Structure) Structure sourcefile
227
228 let parse_interface ~tool_name sourcefile =
229 parse_file ~tool_name Ast_invariants.signature
230 (parse Signature) Signature sourcefile
231