Source file migrate_parsetree_driver.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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
open Migrate_parsetree_versions
module Ast_io = Migrate_parsetree_ast_io
(** {1 State a rewriter can access} *)
type extra = ..
type config = {
tool_name: string;
include_dirs : string list;
load_path : string list;
debug : bool;
for_package : string option;
extras : extra list;
}
let make_config ~tool_name ?(include_dirs=[]) ?(load_path=[]) ?(debug=false)
?for_package ?(extras=[]) () =
{ tool_name
; include_dirs
; load_path
; debug
; for_package
; extras
}
type cookie = Cookie : 'types ocaml_version * 'types get_expression -> cookie
type cookies = (string, cookie) Hashtbl.t
let create_cookies () = Hashtbl.create 3
let global_cookie_table = create_cookies ()
let get_cookie table name version =
match
match Hashtbl.find table name with
| result -> Some result
| exception Not_found ->
match Ast_mapper.get_cookie name with
| Some expr -> Some (Cookie ((module OCaml_current), expr))
| None ->
match Hashtbl.find global_cookie_table name with
| result -> Some result
| exception Not_found -> None
with
| None -> None
| Some (Cookie (version', expr)) ->
Some ((migrate version' version).copy_expression expr)
let set_cookie table name version expr =
Hashtbl.replace table name (Cookie (version, expr))
let set_global_cookie name version expr =
set_cookie global_cookie_table name version expr
let apply_cookies table =
Hashtbl.iter (fun name (Cookie (version, expr)) ->
Ast_mapper.set_cookie name
((migrate version (module OCaml_current)).copy_expression expr)
) table
let initial_state () =
{
tool_name = Ast_mapper.tool_name ();
include_dirs = !Clflags.include_dirs;
load_path = Migrate_parsetree_compiler_functions.get_load_paths ();
debug = !Clflags.debug;
for_package = !Clflags.for_package;
extras = [];
}
(** {1 Registering rewriters} *)
type 'types rewriter = config -> cookies -> 'types get_mapper
type rewriter_group =
Rewriters : 'types ocaml_version * (string * 'types rewriter) list -> rewriter_group
let rewriter_group_names (Rewriters (_, l)) = List.map fst l
let uniq_rewriter = Hashtbl.create 7
module Pos_map = Map.Make(struct
type t = int
let compare : int -> int -> t = compare
end)
let registered_rewriters = ref Pos_map.empty
let all_rewriters () =
Pos_map.bindings !registered_rewriters
|> List.map (fun (_, r) -> !r)
|> List.concat
let uniq_arg = Hashtbl.create 7
let registered_args_reset = ref []
let registered_args = ref []
let () =
let set_cookie s =
match String.index s '=' with
| exception _ ->
raise (Arg.Bad "invalid cookie, must be of the form \"<name>=<expr>\"")
| i ->
let name = String.sub s 0 i in
let value = String.sub s (i + 1) (String.length s - i - 1) in
let input_name = "<command-line>" in
Location.input_name := input_name;
let lexbuf = Lexing.from_string value in
lexbuf.Lexing.lex_curr_p <-
{ Lexing.
pos_fname = input_name
; pos_lnum = 1
; pos_bol = 0
; pos_cnum = 0
};
let expr = Parse.expression lexbuf in
set_global_cookie name (module OCaml_current) expr
in
registered_args :=
("--cookie", Arg.String set_cookie,
"NAME=EXPR Set the cookie NAME to EXPR") :: !registered_args
type ('types, 'version, 'rewriter) is_rewriter =
| Is_rewriter : ('types, 'types ocaml_version, 'types rewriter) is_rewriter
let add_rewriter
(type types) (type version) (type rewriter)
(Is_rewriter : (types, version, rewriter) is_rewriter)
(version : version) name (rewriter : rewriter) =
let rec add_rewriter = function
| [] -> [Rewriters (version, [name, rewriter])]
| (Rewriters (version', rewriters) as x) :: xs ->
match compare_ocaml_version version version' with
| Eq -> Rewriters (version', (name, rewriter) :: rewriters) :: xs
| Lt -> Rewriters (version, [name, rewriter]) :: x :: xs
| Gt -> x :: add_rewriter xs
in
add_rewriter
let register ~name ?reset_args ?(args=[]) ?(position=0) version rewriter =
if name = "" then
invalid_arg "Migrate_parsetree_driver.register: name is empty";
if Hashtbl.mem uniq_rewriter name then
invalid_arg ("Migrate_parsetree_driver.register: rewriter " ^ name ^ " has already been registered")
else Hashtbl.add uniq_rewriter name ();
List.iter (fun (arg_name, _, _) ->
match Hashtbl.find uniq_arg arg_name with
| other_rewriter ->
invalid_arg (Printf.sprintf
"Migrate_parsetree_driver.register: argument %s is used by %s and %s" arg_name name other_rewriter)
| exception Not_found ->
Hashtbl.add uniq_arg arg_name name
) args;
begin match reset_args with
| None -> ()
| Some f -> registered_args_reset := f :: !registered_args_reset
end;
registered_args := List.rev_append args !registered_args;
let r =
try
Pos_map.find position !registered_rewriters
with Not_found ->
let r = ref [] in
registered_rewriters := Pos_map.add position r !registered_rewriters;
r
in
r := add_rewriter Is_rewriter version name rewriter !r
let registered_args () = List.rev !registered_args
let reset_args () = List.iter (fun f -> f ()) !registered_args_reset
(** {1 Accessing or running registered rewriters} *)
type ('types, 'version, 'tree) is_signature =
Signature : ('types, 'types ocaml_version, 'types get_signature) is_signature
type ('types, 'version, 'tree) is_structure =
Structure : ('types, 'types ocaml_version, 'types get_structure) is_structure
type some_structure =
| Str : (module Migrate_parsetree_versions.OCaml_version with
type Ast.Parsetree.structure = 'concrete) * 'concrete -> some_structure
type some_signature =
| Sig : (module Migrate_parsetree_versions.OCaml_version with
type Ast.Parsetree.signature = 'concrete) * 'concrete -> some_signature
let migrate_some_structure dst (Str ((module Version), st)) =
(migrate (module Version) dst).copy_structure st
let migrate_some_signature dst (Sig ((module Version), sg)) =
(migrate (module Version) dst).copy_signature sg
let rec rewrite_signature
: type types version tree.
config -> cookies ->
(types, version, tree) is_signature -> version -> tree ->
rewriter_group list -> some_signature
= fun (type types) (type version) (type tree)
config cookies
(Signature : (types, version, tree) is_signature)
(version : version)
(tree : tree)
-> function
| [] ->
let (module Version) = version in
Sig ((module Version), tree)
| Rewriters (version', rewriters) :: rest ->
let rewrite (_name, rewriter) tree =
let (module Version) = version' in
Version.Ast.map_signature (rewriter config cookies) tree
in
let tree = (migrate version version').copy_signature tree in
let tree = List.fold_right rewrite rewriters tree in
rewrite_signature config cookies Signature version' tree rest
let rewrite_signature config version sg =
let cookies = create_cookies () in
let sg =
rewrite_signature config cookies Signature version sg
(all_rewriters ())
in
apply_cookies cookies;
sg
let rec rewrite_structure
: type types version tree.
config -> cookies ->
(types, version, tree) is_structure -> version -> tree ->
rewriter_group list -> some_structure
= fun (type types) (type version) (type tree)
config cookies
(Structure : (types, version, tree) is_structure)
(version : version)
(tree : tree)
-> function
| [] ->
let (module Version) = version in
Str ((module Version), tree)
| Rewriters (version', rewriters) :: rest ->
let rewriter (_name, rewriter) tree =
let (module Version) = version' in
Version.Ast.map_structure (rewriter config cookies) tree
in
let tree = (migrate version version').copy_structure tree in
let tree = List.fold_right rewriter rewriters tree in
rewrite_structure config cookies Structure version' tree rest
let rewrite_structure config version st =
let cookies = create_cookies () in
let st =
rewrite_structure config cookies Structure version st
(all_rewriters ())
in
apply_cookies cookies;
st
let exit_or_raise ~exit_on_error f =
if not exit_on_error then
f ()
else
try
f ()
with
| Arg.Help text ->
print_string text;
exit 0
| Arg.Bad text ->
prerr_string text;
exit 2
| exn ->
Location.report_exception Format.err_formatter exn;
exit 1
let run_as_ast_mapper ?(exit_on_error = true) args =
let spec = registered_args () in
let args, usage =
let me = Filename.basename Sys.executable_name in
let args = match args with "--as-ppx" :: args -> args | args -> args in
(Array.of_list (me :: args),
Printf.sprintf "%s [options] <input ast file> <output ast file>" me)
in
reset_args ();
exit_or_raise ~exit_on_error begin fun () ->
Arg.parse_argv ~current:(ref 0) args spec
(fun arg -> raise (Arg.Bad (Printf.sprintf "invalid argument %S" arg)))
usage;
OCaml_current.Ast.make_top_mapper
~signature:(fun sg ->
let config = initial_state () in
rewrite_signature config (module OCaml_current) sg
|> migrate_some_signature (module OCaml_current)
)
~structure:(fun str ->
let config = initial_state () in
rewrite_structure config (module OCaml_current) str
|> migrate_some_structure (module OCaml_current)
)
end
let protectx x ~finally ~f =
match f x with
| y -> finally x; y
| exception e -> finally x; raise e
let with_file_in fn ~f =
protectx (open_in_bin fn) ~finally:close_in ~f
let with_file_out fn ~f =
protectx (open_out_bin fn) ~finally:close_out ~f
type ('a, 'b) intf_or_impl =
| Intf of 'a
| Impl of 'b
type file_kind =
| Kind_intf
| Kind_impl
| Kind_unknown
let guess_file_kind fn =
if Filename.check_suffix fn ".ml" then
Kind_impl
else if Filename.check_suffix fn ".mli" then
Kind_intf
else
Kind_unknown
let check_kind fn ~expected ~got =
let describe = function
| Kind_intf -> "interface"
| Kind_impl -> "implementation"
| Kind_unknown -> "unknown file"
in
match expected, got with
| Kind_impl, Kind_impl
| Kind_intf, Kind_intf
| Kind_unknown, _ -> ()
| _ ->
Location.raise_errorf ~loc:(Location.in_file fn)
"Expected an %s got an %s instead"
(describe expected)
(describe got)
let load_file (kind, fn) =
with_file_in fn ~f:(fun ic ->
match Ast_io.from_channel ic with
| Ok (fn, Ast_io.Intf ((module V), sg)) ->
check_kind fn ~expected:kind ~got:Kind_intf;
Location.input_name := fn;
let sg = (migrate (module V) (module OCaml_current)).copy_signature sg in
let migrate_back sg =
Ast_io.Intf
((module V),
(migrate (module OCaml_current) (module V)).copy_signature sg)
in
(fn, Intf (sg, migrate_back))
| Ok (fn, Ast_io.Impl ((module V), st)) ->
check_kind fn ~expected:kind ~got:Kind_impl;
Location.input_name := fn;
let st = (migrate (module V) (module OCaml_current)).copy_structure st in
let migrate_back st =
Ast_io.Impl
((module V),
(migrate (module OCaml_current) (module V)).copy_structure st)
in
(fn, Impl (st, migrate_back))
| Error (Ast_io.Unknown_version _) ->
Location.raise_errorf ~loc:(Location.in_file fn)
"File is a binary ast for an unknown version of OCaml"
| Error (Ast_io.Not_a_binary_ast prefix_read_from_file) ->
let lexbuf = Lexing.from_channel ic in
let len = String.length prefix_read_from_file in
String.blit prefix_read_from_file 0 lexbuf.Lexing.lex_buffer 0 len;
lexbuf.Lexing.lex_buffer_len <- len;
lexbuf.Lexing.lex_curr_p <-
{ Lexing.
pos_fname = fn
; pos_lnum = 1
; pos_bol = 0
; pos_cnum = 0
};
Location.input_name := fn;
let kind =
match kind with
| Kind_impl -> Kind_impl
| Kind_intf -> Kind_intf
| Kind_unknown -> guess_file_kind fn
in
match kind with
| Kind_impl ->
let migrate_back st = Ast_io.Impl ((module OCaml_current), st) in
(fn, Impl (Parse.implementation lexbuf, migrate_back))
| Kind_intf ->
let migrate_back sg = Ast_io.Intf ((module OCaml_current), sg) in
(fn, Intf (Parse.interface lexbuf, migrate_back))
| Kind_unknown ->
Location.raise_errorf ~loc:(Location.in_file fn)
"I can't decide whether %s is an implementation or interface file"
fn)
let with_output ?bin output ~f =
match output with
| None ->
begin match bin with
| Some bin -> set_binary_mode_out stdout bin
| None -> ()
end;
f stdout
| Some fn -> with_file_out fn ~f
type output_mode =
| Pretty_print
| Dump_ast
| Null
let process_file ~config ~output ~output_mode ~embed_errors file =
let fn, ast = load_file file in
let ast, binary_ast =
match ast with
| Intf (sg, migrate_back) ->
let sg = Ast_mapper.drop_ppx_context_sig ~restore:true sg in
let sg =
try
rewrite_signature config (module OCaml_current) sg
|> migrate_some_signature (module OCaml_current)
with exn when embed_errors ->
match Migrate_parsetree_compiler_functions.error_of_exn exn with
| None -> raise exn
| Some error ->
[ Ast_helper.Sig.extension ~loc:Location.none
(Ast_mapper.extension_of_error error) ]
in
let binary_sg =
Ast_mapper.add_ppx_context_sig ~tool_name:config.tool_name sg in
(Intf sg, migrate_back binary_sg)
| Impl (st, migrate_back) ->
let st = Ast_mapper.drop_ppx_context_str ~restore:true st in
let st =
try
rewrite_structure config (module OCaml_current) st
|> migrate_some_structure (module OCaml_current)
with exn when embed_errors ->
match Migrate_parsetree_compiler_functions.error_of_exn exn with
| None -> raise exn
| Some error ->
[ Ast_helper.Str.extension ~loc:Location.none
(Ast_mapper.extension_of_error error) ]
in
let binary_st =
Ast_mapper.add_ppx_context_str ~tool_name:config.tool_name st in
(Impl st, migrate_back binary_st)
in
match output_mode with
| Dump_ast ->
with_output ~bin:true output ~f:(fun oc ->
Ast_io.to_channel oc fn binary_ast)
| Pretty_print ->
with_output output ~f:(fun oc ->
let ppf = Format.formatter_of_out_channel oc in
(match ast with
| Intf sg -> Pprintast.signature ppf sg
| Impl st -> Pprintast.structure ppf st);
Format.pp_print_newline ppf ())
| Null ->
()
let print_transformations () =
let print_group name = function
| [] -> ()
| names ->
Printf.printf "%s:\n" name;
List.iter (Printf.printf "%s\n") names
in
all_rewriters ()
|> List.map rewriter_group_names
|> List.concat
|> print_group "Registered Transformations";
Ppx_derivers.derivers ()
|> List.map (fun (x, _) -> x)
|> print_group "Registered Derivers"
let run_as_standalone_driver ~exit_on_error argv =
let request_print_transformations = ref false in
let output = ref None in
let output_mode = ref Pretty_print in
let output_mode_arg = ref "" in
let files = ref [] in
let embed_errors = ref false in
let embed_errors_arg = ref "" in
let spec =
let fail fmt = Printf.ksprintf (fun s -> raise (Arg.Bad s)) fmt in
let incompatible a b = fail "%s and %s are incompatible" a b in
let as_ppx () = fail "--as-ppx must be passed as first argument" in
let set_embed_errors arg =
if !output_mode = Null then incompatible !output_mode_arg arg;
embed_errors := true;
embed_errors_arg := arg
in
let set_output_mode arg mode =
match !output_mode, mode with
| Pretty_print, _ ->
if mode = Null && !embed_errors then
incompatible !embed_errors_arg arg;
if mode = Null && !output <> None then
incompatible "-o" arg;
output_mode := mode;
output_mode_arg := arg
| _, Pretty_print -> assert false
| Dump_ast, Dump_ast | Null, Null -> ()
| _ -> incompatible !output_mode_arg arg
in
let set_output fn =
if !output_mode = Null then incompatible !output_mode_arg "-o";
output := Some fn
in
let as_pp () =
let arg = "--as-pp" in
set_output_mode arg Dump_ast;
set_embed_errors arg
in
[ "--as-ppx", Arg.Unit as_ppx,
" Act as a -ppx rewriter"
; "--as-pp", Arg.Unit as_pp,
" Shorthand for: --dump-ast --embed-errors"
; "--dump-ast", Arg.Unit (fun () -> set_output_mode "--dump-ast" Dump_ast),
" Output a binary AST instead of source code"
; "--null", Arg.Unit (fun () -> set_output_mode "--null" Null),
" Output nothing, just report errors"
; "-o", Arg.String set_output,
"FILE Output to this file instead of the standard output"
; "--intf", Arg.String (fun fn -> files := (Kind_intf, fn) :: !files),
"FILE Treat FILE as a .mli file"
; "--impl", Arg.String (fun fn -> files := (Kind_impl, fn) :: !files),
"FILE Treat FILE as a .ml file"
; "--embed-errors", Arg.Unit (fun () -> set_embed_errors "--embed-errors"),
" Embed error reported by rewriters into the AST"
; "--print-transformations", Arg.Set request_print_transformations,
" Print registered transformations in their order of executions"
]
in
let spec = Arg.align (spec @ registered_args ()) in
let me = Filename.basename Sys.executable_name in
let usage = Printf.sprintf "%s [options] [<files>]" me in
exit_or_raise ~exit_on_error begin fun () ->
reset_args ();
Arg.parse_argv ~current:(ref 0) argv spec (fun anon ->
files := (Kind_unknown, anon) :: !files) usage;
if !request_print_transformations then
print_transformations ()
else
let output = !output in
let output_mode = !output_mode in
let embed_errors = !embed_errors in
let config =
{ tool_name = "migrate_driver"
; include_dirs = []
; load_path = []
; debug = false
; for_package = None
; extras = []
}
in
List.iter (process_file ~config ~output ~output_mode ~embed_errors)
(List.rev !files)
end
let run_as_ppx_rewriter ?(exit_on_error = true) ?(argv = Sys.argv) () =
let a = argv in
let n = Array.length a in
exit_or_raise ~exit_on_error begin fun () ->
if n <= 2 then begin
let me = Filename.basename Sys.executable_name in
Arg.usage_string (registered_args ())
(Printf.sprintf "%s [options] <input ast file> <output ast file>" me);
|> fun s -> raise (Arg.Bad s)
end;
Ast_mapper.apply ~source:a.(n - 2) ~target:a.(n - 1)
(run_as_ast_mapper (Array.to_list (Array.sub a 1 (n - 3))))
end
let run_main ?(exit_on_error = true) ?(argv = Sys.argv) () =
if Array.length argv >= 2 && argv.(1) = "--as-ppx" then
run_as_ppx_rewriter ~exit_on_error ~argv ()
else
run_as_standalone_driver ~exit_on_error argv