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
open Import
let with_output fn ~binary ~f =
match fn with
| None | Some "-" ->
set_binary_mode_out stdout binary;
f stdout
| Some fn -> Out_channel.with_file fn ~binary ~f
module Kind = struct
type t = Intf | Impl
let of_filename fn : t option =
if Stdlib.Filename.check_suffix fn ".ml" then Some Impl
else if Stdlib.Filename.check_suffix fn ".mli" then Some Intf
else None
let describe = function Impl -> "implementation" | Intf -> "interface"
let equal : t -> t -> bool = Poly.equal
end
module Intf_or_impl = struct
type t = Intf of signature | Impl of structure
let map t (map : Ast_traverse.map) =
match t with
| Impl x -> Impl (map#structure x)
| Intf x -> Intf (map#signature x)
let map_with_context t (map : _ Ast_traverse.map_with_context) ctx =
match t with
| Impl x -> Impl (map#structure ctx x)
| Intf x -> Intf (map#signature ctx x)
let kind : _ -> Kind.t = function Intf _ -> Intf | Impl _ -> Impl
end
module Ast_io = struct
type input_version = (module OCaml_version)
let fall_back_input_version = (module Compiler_version : OCaml_version)
type t = {
input_name : string;
input_version : input_version;
ast : Intf_or_impl.t;
}
type read_error =
| Not_a_binary_ast
| Unknown_version of string * input_version
| Source_parse_error of Location.Error.t * input_version
| System_error of Location.Error.t * input_version
type input_source = Stdin | File of string
type input_kind = Possibly_source of Kind.t * string | Necessarily_binary
let read_error_to_string (error : read_error) =
match error with
| Not_a_binary_ast -> "Error: Not a binary ast"
| Unknown_version (s, _) -> "Error: Unknown version " ^ s
| Source_parse_error (loc, _) ->
"Source parse error:" ^ Location.Error.message loc
| System_error (loc, _) -> "System error: " ^ Location.Error.message loc
let parse_source_code ~(kind : Kind.t) ~input_name ~prefix_read_from_source ic
=
let input_version = (module Compiler_version : OCaml_version) in
try
let all_source = prefix_read_from_source ^ In_channel.input_all ic in
let lexbuf = Lexing.from_string all_source in
lexbuf.lex_curr_p <- { lexbuf.lex_curr_p with pos_fname = input_name };
Astlib.Location.set_input_lexbuf (Some lexbuf);
Skip_hash_bang.skip_hash_bang lexbuf;
let ast : Intf_or_impl.t =
match kind with
| Intf -> Intf (Parse.interface lexbuf)
| Impl -> Impl (Parse.implementation lexbuf)
in
Ok { input_name; input_version; ast }
with exn -> (
match Location.Error.of_exn exn with
| None -> raise exn
| Some error -> Error (Source_parse_error (error, input_version)))
let magic_length = String.length Astlib.Config.ast_impl_magic_number
let read_magic ic =
let buf = Bytes.create magic_length in
let len = input ic buf 0 magic_length in
let s = Bytes.sub_string buf ~pos:0 ~len in
if len = magic_length then Ok s else Error s
let set_input_lexbuf input_name =
let set_input_lexbuf ic =
let source = In_channel.input_all ic in
let lexbuf = Lexing.from_string source in
Astlib.Location.set_input_lexbuf (Some lexbuf);
lexbuf
in
match In_channel.with_file ~binary:true input_name ~f:set_input_lexbuf with
| (_ : Lexing.lexbuf) -> ()
| exception Sys_error _ -> ()
let from_channel ch ~input_kind =
let handle_non_binary prefix_read_from_source =
match input_kind with
| Possibly_source (kind, input_name) ->
parse_source_code ~kind ~input_name ~prefix_read_from_source ch
| Necessarily_binary -> Error Not_a_binary_ast
in
set_binary_mode_in ch true;
match read_magic ch with
| Error s -> handle_non_binary s
| Ok s -> (
match Find_version.from_magic s with
| Intf (module Input_version : OCaml_version) ->
let input_name : string = input_value ch in
let ast = input_value ch in
let module Input_to_ppxlib = Convert (Input_version) (Js) in
set_input_lexbuf input_name;
let ast = Intf_or_impl.Intf (Input_to_ppxlib.copy_signature ast) in
Ok
{
input_name;
input_version = (module Input_version : OCaml_version);
ast;
}
| Impl (module Input_version : OCaml_version) ->
let input_name : string = input_value ch in
let ast = input_value ch in
let module Input_to_ppxlib = Convert (Input_version) (Js) in
set_input_lexbuf input_name;
let ast = Intf_or_impl.Impl (Input_to_ppxlib.copy_structure ast) in
Ok
{
input_name;
input_version = (module Input_version : OCaml_version);
ast;
}
| Unknown ->
if
String.equal
(String.sub s ~pos:0 ~len:9)
(String.sub Astlib.Config.ast_impl_magic_number ~pos:0 ~len:9)
|| String.equal
(String.sub s ~pos:0 ~len:9)
(String.sub Astlib.Config.ast_intf_magic_number ~pos:0 ~len:9)
then Error (Unknown_version (s, fall_back_input_version))
else handle_non_binary s)
let read input_source ~input_kind =
try
match input_source with
| Stdin ->
set_binary_mode_in stdin true;
from_channel stdin ~input_kind
| File fn -> In_channel.with_file fn ~f:(from_channel ~input_kind)
with exn -> (
match Location.Error.of_exn exn with
| None -> raise exn
| Some error -> Error (System_error (error, fall_back_input_version)))
let write oc { input_name; input_version = (module Input_version); ast }
~add_ppx_context =
let module Ppxlib_to_input = Convert (Js) (Input_version) in
let module Ocaml_to_input = Convert (Compiler_version) (Input_version) in
match ast with
| Intf sg ->
let sg =
if add_ppx_context then
Selected_ast.To_ocaml.copy_signature sg
|> Astlib.Ast_metadata.add_ppx_context_sig ~tool_name:"ppx_driver"
|> Ocaml_to_input.copy_signature
else Ppxlib_to_input.copy_signature sg
in
output_string oc Input_version.Ast.Config.ast_intf_magic_number;
output_value oc input_name;
output_value oc sg
| Impl st ->
let st =
if add_ppx_context then
Selected_ast.To_ocaml.copy_structure st
|> Astlib.Ast_metadata.add_ppx_context_str ~tool_name:"ppx_driver"
|> Ocaml_to_input.copy_structure
else Ppxlib_to_input.copy_structure st
in
output_string oc Input_version.Ast.Config.ast_impl_magic_number;
output_value oc input_name;
output_value oc st
module Read_bin = struct
type ast = Intf of signature | Impl of structure
type t = { ast : ast; input_name : string }
let read_binary fn =
match
In_channel.with_file fn ~f:(from_channel ~input_kind:Necessarily_binary)
with
| Ok { ast; input_name; _ } ->
let ast =
match ast with
| Impl structure -> Impl structure
| Intf signature -> Intf signature
in
Ok { ast; input_name }
| Error e -> Error (read_error_to_string e)
let get_ast t = t.ast
let get_input_name t = t.input_name
end
end
module System = struct
let run_preprocessor ~pp ~input ~output =
let command =
Printf.sprintf "%s %s > %s" pp
(if String.equal input "-" then "" else Stdlib.Filename.quote input)
(Stdlib.Filename.quote output)
in
if Stdlib.Sys.command command = 0 then Ok ()
else Error (command, Ast_io.fall_back_input_version)
end
let print_as_compiler_source ppf ast =
let module Ppxlib_to_compiler = Convert (Js) (Compiler_version) in
match (ast : Intf_or_impl.t) with
| Intf sg ->
let sg = Ppxlib_to_compiler.copy_signature sg in
Astlib.Compiler_pprintast.signature ppf sg
| Impl st ->
let st = Ppxlib_to_compiler.copy_structure st in
Astlib.Compiler_pprintast.structure ppf st