New inference: flatten unions when solving for lower bounds
[hiphop-php.git] / hphp / hack / src / hh_single_parse.ml
blobb80838cb15e52a3304aeae1a0a36d582600db73c
1 (**
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 module Hh_debug = Debug
11 open Core_kernel
12 module Lowerer = Full_fidelity_ast
13 module Syntax = Full_fidelity_positioned_syntax
14 module SyntaxKind = Full_fidelity_syntax_kind
15 module SourceText = Full_fidelity_source_text
16 module SyntaxTree = Full_fidelity_syntax_tree
17 .WithSyntax(Syntax)
19 let purpose = "Read a single Hack file and produce the resulting S-Expression."
20 let extra = "(Options for development / parser selection and comparisson.)"
21 let usage = Printf.sprintf
22 "Usage: %s <options> filename\n%s\n%s"
23 Sys.argv.(0)
24 purpose
25 extra
27 type parser_return = Parser_return.t * float
28 type result =
29 | CmpDifferent
30 | Unsupported
31 | ParseError
33 let exit_code : result -> int = function
34 | ParseError -> 1
35 | Unsupported -> 3
36 | CmpDifferent -> 42
38 type parser_config =
39 | FFP
40 | ValidatedFFP
41 | Benchmark_batch of int
43 let exit_with : result -> 'a = fun r -> exit (exit_code r)
45 let handle_errors : Errors.t -> unit = fun errorl ->
46 let open Errors in
47 let print_err err = Out_channel.output_string stderr (to_string (to_absolute err)) in
48 if is_empty errorl
49 then ()
50 else begin
51 iter_error_list print_err errorl;
52 exit_with ParseError
53 end
56 let run_ffp
57 ?(iters = 0)
58 ~codegen
59 ~allow_malformed
60 ~pocket_universes
61 (file : Relative_path.t)
62 : Lowerer.result =
63 let env =
64 Lowerer.make_env
65 ~codegen
66 ~include_line_comments:true
67 ~fail_open:allow_malformed
68 ~keep_errors:(not allow_malformed)
69 ~pocket_universes
70 file
72 if iters < 1 then () else
73 for i = 1 to iters do
74 ignore(Lowerer.from_file env : Lowerer.result);
75 done;
76 Lowerer.from_file env
77 let run_validated_ffp : bool -> Relative_path.t -> Lowerer.result =
78 fun pocket_universes file ->
79 let open SyntaxTree in
80 let source_text = SourceText.from_file file in
81 let tree = make source_text in
82 let script = root tree in
83 let validated =
84 try
85 Syntax.Validated.validate_script script
86 with
87 | Syntax.Validated.Validation_failure (k,s) as e -> begin
88 Printf.eprintf "FAILURE: expected: %s actual: %s\n"
89 (Option.value_map ~f:SyntaxKind.to_string ~default:"Some token" k)
90 (SyntaxKind.to_string (Syntax.kind s));
91 raise e
92 end
94 let invalidated = Syntax.Validated.invalidate_script validated in
95 let revalidated = Syntax.Validated.validate_script invalidated in
96 assert (validated = revalidated); (* Idempotence *after* validation *)
97 assert (script = invalidated); (* Idempotence *of* validation *)
98 let invalidated =
99 Full_fidelity_editable_positioned_syntax.from_positioned_syntax
100 invalidated in
101 let is_hh_file = is_hack tree in
102 let env = Lowerer.make_env ~is_hh_file ~pocket_universes file in
103 let comments = Lowerer.scour_comments_and_add_fixmes env source_text script in
104 let module Lowerer = Lowerer.WithPositionedSyntax(Full_fidelity_editable_positioned_syntax) in
105 Lowerer.lower env ~source_text ~script:invalidated comments
107 let measure : (unit -> 'a) -> 'a * float = fun f ->
108 let start = Unix.gettimeofday () in
109 let res = f () in
110 let stop = Unix.gettimeofday () in
111 res, stop -. start
114 let run_parsers
115 dumper
116 (file : Relative_path.t)
117 (conf : parser_config)
118 ~hash
119 ~codegen
120 ~allow_malformed
121 ~dump_nast
122 ~pocket_universes
124 match conf with
125 | FFP ->
126 let res = run_ffp ~codegen ~allow_malformed ~pocket_universes file in
127 let ast = res.Lowerer.ast in
128 let output =
129 if dump_nast then (
130 let nast = Ast_to_nast.convert ast in
131 Nast.show_program nast
132 ) else
133 if not hash then dumper ast
134 else
135 let decl_hash = Ast_utils.generate_ast_decl_hash ast in
136 OpaqueDigest.to_hex decl_hash
138 Printf.printf "%s" output
139 | ValidatedFFP ->
140 let res = run_validated_ffp pocket_universes file in
141 Printf.printf "%s" (dumper res.Lowerer.ast)
142 | Benchmark_batch iters ->
143 let filename = Relative_path.S.to_string file in
144 let _, duration =
145 try (measure (fun () ->
146 run_ffp ~codegen ~iters ~allow_malformed:false ~pocket_universes file))
147 with _ -> begin
148 Printf.printf "FAIL, %s\n" filename;
149 exit_with ParseError
152 let res = Printf.sprintf
153 "PASS, %s, %12.10f\n"
154 filename duration in
155 print_endline res
157 let () =
158 Printexc.record_backtrace true;
159 let use_parser = ref "ffp" in
160 let hash = ref false in
161 let dumper = ref Hh_debug.dump_ast in
162 let filename = ref "" in
163 let num_runs = ref 100 in
164 let benchmark_files = ref [] in
165 let no_codegen = ref false in
166 let allow_malformed = ref false in
167 let dump_nast = ref false in
168 let pocket_universes = ref false in
169 Arg.(parse
170 [ ("--hash", Set hash,
171 "Get the decl level parsing hash of a given file "
173 ; ("--sorted", Unit (fun () -> dumper := Hh_debug.dump_sorted_ast),
174 "When using the `compare` parser, the (lexicographically) sort the " ^
175 "S-Expressions before diffing"
177 ; ("--show-pos", Unit (fun () -> Sof.show_pos := true),
178 "Show positional information on the AST"
180 ; ("--num-runs", Int (fun x -> num_runs := x),
181 "How many times to benchmark if in benchmark mode [default: 100]"
183 ; ("--benchmark_batch", Rest (fun fn -> benchmark_files := fn::!benchmark_files),
184 "Run benchmarking on a list of files"
186 ; ("--no-codegen", Set no_codegen,
187 "Turn off codegen mode when parsing with FFP [default: false]"
189 ; ("--nast", Set dump_nast,
190 "Convert to NAST and print [default: false]"
192 ; ("--allow-malformed", Set allow_malformed,
193 "Allow malformed files (such as for testing IDE services) [default: false]"
195 ; ("--pocket-universes", Set pocket_universes,
196 "Enables support for Pocket Universes [default: false]"
198 ]) (fun fn -> filename := fn) usage;
199 let parse_function = match !use_parser with
200 | _ when !benchmark_files <> [] -> Benchmark_batch !num_runs
201 | "ffp" -> FFP
202 | "validated" -> ValidatedFFP
203 | s -> raise (Failure (Printf.sprintf "Unknown parser '%s'\n" s))
205 if String.length !filename = 0 && !benchmark_files = [] then failwith "No filename given";
206 EventLogger.init EventLogger.Event_logger_fake 0.0;
207 let handle = SharedMem.init ~num_workers:0 GlobalConfig.default_sharedmem_config in
208 ignore (handle: SharedMem.handle);
209 let dumper ast = !dumper (Ast.AProgram ast) in
210 let parse_file fn =
211 let file = Relative_path.create Relative_path.Dummy fn in
212 run_parsers
213 dumper
214 file
215 ~hash:!hash
216 parse_function
217 ~codegen:(not !no_codegen)
218 ~allow_malformed:!allow_malformed
219 ~dump_nast:!dump_nast
220 ~pocket_universes:!pocket_universes
222 if !benchmark_files <> [] then
223 List.iter ~f:parse_file !benchmark_files
224 else
225 Unix.handle_unix_error (parse_file) !filename