2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
10 (*****************************************************************************)
11 (* Imported modules. *)
12 (*****************************************************************************)
16 exception Format_error
20 (*****************************************************************************)
21 (* The precedence of the Tarrow operator (->). *)
22 (*****************************************************************************)
24 let tarrow_prec = snd
(Parser_hack.get_priority Tarrow
)
25 let tpipe_prec = snd
(Parser_hack.get_priority Tpipe
)
27 (*****************************************************************************)
29 (*****************************************************************************)
32 (* The last emitted token was a new line *)
35 (* The last emitted token was XHP text *)
38 (* The last emitted token was a space *)
44 (* Absolute character position in the input file. *)
48 (* Line number in the input file *)
51 (* Beginning of an indivisible formatting block *)
54 (* Meta-data to be able to reconcile the input file and the
55 * formatted output (useful for Format_diff)
57 type source_pos
= char_pos
* source_tag
60 (* The number of spaces for the margin *)
63 (* The last kind of token emitted *)
64 last
: char_kind
ref ;
66 (* The last token emitted *)
67 last_token
: Lexer_hack.token
ref ;
69 (* The string representing last token emitted *)
70 last_str
: string ref ;
72 (* The string that must be outputted when printing
73 * the last token (can be different from last_str, cf function token) *)
74 last_out
: string ref ;
76 (* The output buffer *)
79 (* The path of the current file *)
82 (* The state of the lexer *)
83 lexbuf
: Lexing.lexbuf
;
85 (* The line number of the input *)
88 (** The number of non-whitespace tokens consumed in this input line.
90 * This is needed for pretty, optional linebreaks for the pipe operator.
92 * For example, these linebreaks between the pipes in the input
93 * are preserved in the output, despite never exceeding 80 char width:
98 * See also pipe operator tests.
100 input_line_non_ws_token_count
: int ref ;
102 (* The precedence of the current binary operator (0 otherwise) *)
105 (* The output character position (from the beginning of the line) *)
108 (* The output absolute character position *)
111 (* The char position after which we break (typically 80).
112 * It can be different from char_size, because in some cases we need
113 * room to leave the semi-colon on the same line.
118 (* The precedence of the operator we should try to break *)
121 (* The output line number *)
124 (* > 0 if the current try failed
125 * = 0 if the current not trying anything
126 * < 0 if we should not bother trying but force to break
127 * You should checkout the module Try if this doesn't make sense.
131 (* The depth of nested try_outer we are currently in *)
134 (* True if we are trying to emit something that must fit on one line *)
137 (* True if we should set env.failed on failure *)
140 (* True if we are in an attribute (<< ... >>) *)
143 (* The amount of spaces that must be emitted on the next token
144 * (this is for the margin).
148 (* True if we should stop after a certain position. *)
151 (* True when we should not emit to the buffer (useful for region mode) *)
154 (* The beginning of the region we are trying to format. *)
157 (* The end of the region we are trying to format. *)
160 (* When the "keep_source_pos" option is turned on,
161 * the formatter outputs extra tags in the field source_pos.
162 * There are 2 kinds of tags 'Line and 'Block'
163 * (these tags are used by Format_diff).
165 * The tags of the form 'Line' should be read as: at this
166 * point in the text, the input line number was LINE_NUMBER
167 * (useful to reconcile input/output line numbers).
169 * The tags of the form 'Block' should be read as: we have reached the
170 * beginning or the end of an indivisible block.
172 * This information is useful to know which pieces can be formatted
173 * separately. For example, let's consider the following diff:
179 * It doesn't make sense to only format the line that changed.
180 * The formatter is probably going to regroup the entire array on
181 * one line (because it fits).
182 * Thanks to these extra tags, we know that we have to treat the entire
183 * array as an indivisible entity.
185 keep_source_pos
: bool ;
186 source_pos_l
: source_pos list
ref ;
188 (* When no_trailing_commas is false (default), multiline comma separated items
189 include a trailing comma, when it is false, we omit trailing commas. The
190 standard php parser does not support trailing commas, but hhvm does.
192 no_trailing_commas
: bool ;
195 (*****************************************************************************)
196 (* The "saved" environment (to allow backtracking). *)
197 (*****************************************************************************)
201 sv_last
: char_kind
;
202 sv_last_token
: Lexer_hack.token
;
203 sv_last_str
: string ;
204 sv_last_out
: string ;
205 sv_buffer
: Buffer.t
;
206 sv_lexbuf
: Parser_hack.saved_lb
;
214 sv_source_pos_l
: source_pos list
;
217 let empty file lexbuf from to_ keep_source_pos no_trailing_commas
= {
220 last_token
= ref Terror
;
223 buffer
= Buffer.create
256 ;
227 input_line_non_ws_token_count
= ref 0 ;
246 source_pos_l
= ref [] ;
247 no_trailing_commas
= no_trailing_commas
;
250 (* Saves all the references of the environment *)
252 let { margin
; last
; last_token
; buffer
; file
; lexbuf
; lb_line
;
253 input_line_non_ws_token_count
;
254 priority
; char_pos
; abs_pos
; char_break
;
255 char_size
; silent
; one_line
;
256 last_str
; last_out
; keep_source_pos
; source_pos_l
;
257 break_on
; line
; failed
; try_depth
; spaces
;
258 report_fit
; in_attr
; stop
; from
; to_
; no_trailing_commas
} = env
in
259 { sv_margin
= !margin
;
261 sv_buffer
= env
.buffer
;
262 sv_last_token
= !last_token
;
263 sv_last_str
= !last_str
;
264 sv_last_out
= !last_out
;
265 sv_lexbuf
= Parser_hack.save_lexbuf_state lexbuf
;
266 sv_lb_line
= !lb_line
;
267 sv_char_pos
= !char_pos
;
268 sv_abs_pos
= !abs_pos
;
273 sv_source_pos_l
= !source_pos_l
;
276 let restore_env env saved_env
=
277 Parser_hack.restore_lexbuf_state env
.lexbuf saved_env
.sv_lexbuf
;
278 env
.lb_line
:= saved_env
.sv_lb_line
;
279 env
.margin
:= saved_env
.sv_margin
;
280 env
.last
:= saved_env
.sv_last
;
281 env
.last_token
:= saved_env
.sv_last_token
;
282 env
.last_str
:= saved_env
.sv_last_str
;
283 env
.last_out
:= saved_env
.sv_last_out
;
284 env
.char_pos
:= saved_env
.sv_char_pos
;
285 env
.abs_pos
:= saved_env
.sv_abs_pos
;
286 env
.line
:= saved_env
.sv_line
;
287 env
.failed
:= saved_env
.sv_failed
;
288 env
.spaces
:= saved_env
.sv_spaces
;
289 env
.silent
:= saved_env
.sv_silent
;
290 env
.source_pos_l
:= saved_env
.sv_source_pos_l
;
291 { env
with buffer
= saved_env
.sv_buffer
}
293 (*****************************************************************************)
294 (* Consumes the next token.
295 * The logic is a bit complex because of the regions.
296 * If hh_format is called with -from -to options, we want to start/stop
297 * emitting text depending on the position.
298 * The problem is that -from could point to the middle of a token.
299 * When that happens we split the token in 2, the relevant part (the one
300 * we want to emit) is kept in env.last_out, the full string is kept in
302 * Both env.last_out/env.last_str are useful:
303 * -) env.last_out is used to emit the token (that's why we use the function
304 * last_token instead of emitting a string directly)
305 * -) env.last_str is used for the logic of the parser (we can't use a
306 * truncated token for that)
308 (*****************************************************************************)
310 let make_tokenizer next_token env
=
311 let pos = env
.lexbuf
.Lexing.lex_curr_pos
in
312 if pos >= env
.stop
then Teof
else
313 let tok = next_token env
.lexbuf
in
314 let new_pos = env
.lexbuf
.Lexing.lex_curr_pos
in
315 env
.silent
:= (new_pos <= env
.from
|| pos >= env
.to_
- 1);
316 env
.last_token
:= tok;
317 let str_value = Lexing.lexeme env
.lexbuf
in
318 env
.last_str
:= str_value;
319 (* Splitting the token (-from) *)
320 if pos < env
.from
&& new_pos > env
.from
322 let sub_size = new_pos - env
.from
+ 1 in
323 let str_size = String.length
str_value in
324 let start = str_size - sub_size in
325 env
.last_out
:= String.sub
str_value start sub_size;
327 (* Splitting the token (-to) *)
328 else if pos < env
.to_
- 1 && new_pos >= env
.to_
- 1
330 let sub_size = env
.to_
- pos - 1 in
331 env
.last_out
:= String.sub
str_value 0 sub_size;
333 else env
.last_out
:= str_value;
336 env
.lb_line
:= !(env
.lb_line
) + 1;
337 env
.input_line_non_ws_token_count
:= 0
340 env
.input_line_non_ws_token_count
:=
341 !(env
.input_line_non_ws_token_count
) + 1
345 (* Normal tokenizer *)
346 let token = make_tokenizer Lexer_hack.format_token
349 let xhp_token = make_tokenizer Lexer_hack.format_xhptoken
351 (* Comment tokenizer *)
352 let comment_token = make_tokenizer Lexer_hack.format_comment
354 (*****************************************************************************)
356 (*****************************************************************************)
359 if !(env
.last_token
) = Tnewline
360 then env
.lb_line
:= !(env
.lb_line
) - 1;
361 env
.last_token
:= Terror
;
362 env
.input_line_non_ws_token_count
:=
363 max
0 (!(env
.input_line_non_ws_token_count
) - 1);
364 Lexer_hack.back env
.lexbuf
366 (*****************************************************************************)
367 (* Primitives used to look ahead. *)
368 (*****************************************************************************)
370 (* Attempt does not modify the state of the environment *)
372 let buffer = Buffer.create
256 in
373 let saved_env = save_env env
in
374 let f_result = f
{ env
with buffer } in
375 let _ = restore_env env
saved_env in
378 let attempt_keep_lines env f
=
379 attempt env
begin fun env
->
380 let buffer = Buffer.create
256 in
381 let env = { env with buffer } in
382 let line = !(env.line) in
383 let _ = f
{ env with report_fit
= true } in
384 let nbr_lines = !(env.line) - line in
385 buffer, nbr_lines, !(env.failed
)
388 (*****************************************************************************)
389 (* Primitives for branching.
391 * The branching logic always tries to break the outer-most expression.
394 * array(array(array(...)))
395 * should be first rewritten by trying:
406 * However, the logic is a bit complicated because the algorithm is
407 * exponential and that becomes a problem on very large nested arrays.
408 * The solution consist in breaking multiple layers at once when a
409 * certain depth is reached.
411 * Let's consider: array(array(.. array N times ))
412 * If the array breaks at a depth larger than 6 we directly try:
415 * array( ... N/2 times
417 * We preemptively break the array N/2 times, to avoid the exponential.
419 (*****************************************************************************)
423 val one_line
: env -> (env -> unit) -> (env -> unit) -> unit
424 val outer
: env -> (env -> unit) -> (env -> unit) -> unit
427 let try_raw env f1 f2
=
428 let saved_env = save_env env in
429 let buffer = Buffer.create
256 in
430 let env = { env with buffer } in
431 let f1_result = f1
{ env with report_fit
= true } in
432 if !(env.failed
) <= 0
434 Buffer.add_buffer
saved_env.sv_buffer
buffer;
438 let env = restore_env env saved_env in
442 let one_line env f1 f2
=
443 if env.one_line then f1
env else
446 try ignore
(f1
{ env with one_line = true })
452 let outer env f1 f2
=
454 then f1
{ env with try_depth
= env.try_depth
+ 1 }
455 else if env.try_depth
< 0
456 then f2
{ env with try_depth
= env.try_depth
+ 1 }
458 let depth_failed = ref 0 in
459 let big_buffer = ref false in
462 f1
{ env with try_depth
= 1 };
463 big_buffer := Buffer.length
env.buffer > 10_000;
464 depth_failed := !(env.failed
);
468 then f2
{ env with try_depth
= - (!depth_failed / 2) }
470 then f2
{ env with try_depth
= - 3 }
474 (*****************************************************************************)
475 (* Scoring functions.
476 * There are cases where multiple choices could fit. When that's the case,
477 * we pick the one that "looks" nicer.
478 * The "looks" function is pretty subjective ;-)
480 (*****************************************************************************)
482 let rec aligned last_tok count lexbuf
=
483 match Lexer_hack.format_token lexbuf
with
485 | Tspace
-> aligned last_tok count lexbuf
487 let tok = Lexing.lexeme lexbuf
in
488 if last_tok
= ")" && tok = "->" then -100 else
489 let count = if last_tok
= tok && last_tok
= "->"
490 then count + 1 else count in
491 aligned_look_for_newline
tok count lexbuf
493 and aligned_look_for_newline last_tok
count lexbuf
=
494 match Lexer_hack.format_token lexbuf
with
496 | Tnewline
-> aligned last_tok
count lexbuf
497 | _ -> aligned_look_for_newline last_tok
count lexbuf
499 let keep_best env f1 f2
=
500 if env.one_line then f1
env else
501 let env = { env with try_depth
= 0 } in
502 let buffer1, nbr_lines1
, failed1
= attempt_keep_lines env f1
in
503 let buffer2, nbr_lines2
, failed2
= attempt_keep_lines env f2
in
504 if failed1
> 0 then f2
env else
505 if failed2
> 0 then f1
env else
506 let buffer1 = Buffer.contents
buffer1 in
507 let buffer2 = Buffer.contents
buffer2 in
508 (* The logic to select the best solution *)
509 let aligned_count1 = aligned "" 0 (Lexing.from_string
buffer1) in
510 let aligned_count2 = aligned "" 0 (Lexing.from_string
buffer2) in
511 if aligned_count2 > aligned_count1 then f2
env else
512 if aligned_count1 < aligned_count2 then f1
env else
513 if nbr_lines1
<= nbr_lines2
then f1
env else f2
env
515 (*****************************************************************************)
516 (* Returns the current position in the buffer. *)
517 (*****************************************************************************)
520 env.lexbuf
.Lexing.lex_curr_pos
522 (*****************************************************************************)
523 (* Pretty printing primitives.
524 * We don't want to maintain the state of pretty-printer all the time.
525 * This module keeps track of what the margin should be (adds spaces when
526 * needed), removes spaces when they are followed by a new line etc ...
528 (*****************************************************************************)
532 val out
: string -> env -> unit
533 val last_token
: env -> unit
534 val margin_set
: int -> env -> (env -> 'a
) -> 'a
535 val right
: env -> (env -> 'a
) -> 'a
536 val right_fun
: (env -> 'a
) -> env -> 'a
537 val right_n
: int -> env -> (env -> 'a
) -> 'a
538 val force_nl
: env -> unit
539 val newline
: env -> unit
540 val space
: env -> unit
541 val keep_space
: env -> unit
545 let buf_add_char env c
=
546 if not
!(env.silent
) then begin
547 Buffer.add_char
env.buffer c
550 let buf_add_string env s
=
551 if not
!(env.silent
) then begin
552 Buffer.add_string
env.buffer s
555 let add_char_pos env n
=
556 env.char_pos
:= !(env.char_pos
) + n
;
557 env.abs_pos
:= !(env.abs_pos
) + n
;
558 if env.report_fit
&& !(env.char_pos
) >= env.char_break
then begin
559 if env.one_line then raise One_line
;
560 env.failed
:= max
1 (max
!(env.failed
) env.try_depth
)
568 let add_string env s
=
569 buf_add_string env s
;
570 add_char_pos env (String.length s
)
575 env.line := !(env.line) + 1;
578 if env.keep_source_pos
then begin
579 let source_pos = !(env.abs_pos
), Line
!(env.lb_line
) in
580 env.source_pos_l
:= source_pos :: !(env.source_pos_l
)
584 if env.one_line then raise One_line
;
585 if !(env.last
) <> Newline
then force_nl env
588 if !(env.last
) <> Space
then begin
590 env.spaces
:= !(env.spaces
) + 1;
594 assert (!(env.last_token
) = Tspace
);
595 let str = !(env.last_out
) in
597 String.iter
(fun c
-> assert (c
= ' '
)) str;
598 env.spaces
:= !(env.spaces
) + String.length
str
600 let right_n n
env f
=
601 env.margin
:= !(env.margin
) + n
;
602 let result = f
env in
603 env.margin
:= !(env.margin
) - n
;
606 let margin_set n
env f
=
607 let margin_cpy = !(env.margin
) in
609 let result = f
env in
610 env.margin
:= margin_cpy;
613 let right env f
= right_n 2 env f
614 let right_fun f
= fun env -> right env f
617 if !(env.last
) = Newline
then env.spaces
:= !(env.margin
);
618 for i
= 0 to !(env.spaces
) - 1 do
627 out !(env.last_out
) env
633 (*****************************************************************************)
634 (* Some helpers to regroup sequences of pretty-printing functions. *)
635 (*****************************************************************************)
637 let rec seq env = function
639 | f
:: rl
-> f
env; seq env rl
641 let seq_fun l
= fun env -> seq env l
651 let ignore_ f
env = ignore
(f
env)
653 (*****************************************************************************)
654 (* Precedence of binary operators.
655 * We need to maintain that information to break expressions with the lowest
657 * Example: 1 * 2 * 3 + 4
661 * Before we try to to break (1 * 2 * 3).
662 * These functions keep track the precedence of the current operator to later
663 * on prioritize in what order we will break an expression (when necessary).
665 (*****************************************************************************)
667 let set_priority env priority
=
668 { env with priority
}
670 let reset_priority env =
671 { env with priority
= 0 }
673 let with_priority env op f
=
674 let _, prio
= Parser_hack.get_priority op
in
675 let env = set_priority env prio
in
678 (*****************************************************************************)
679 (* Add block tag. Used for --diff mode.
680 * We don't have to worry about Opening or Closing blocks, because the logic
681 * is: whatever is in between 2 blocks is indivisible.
682 * Why is that? Because the place where we add the block tag are the places
683 * where we know it's acceptable to break the indentation.
684 * Think of it this way: block tags tell us where we can break the formatting
685 * given that, whatever is in between two block tags is indivisible.
687 * Note that we only insert Block tags where the existing code has a line
688 * break. See Format_diff.TextBlocks for more details.
690 (*****************************************************************************)
692 let add_block_tag env =
693 assert (!(env.last
) = Newline
);
694 if env.keep_source_pos
&& attempt env token = Tnewline
then begin
695 let source_pos = !(env.abs_pos
), Block
in
696 env.source_pos_l
:= source_pos :: !(env.source_pos_l
)
699 (*****************************************************************************)
701 (*****************************************************************************)
703 let rec skip_spaces env =
706 | Tspace
-> skip_spaces env
709 let rec skip_spaces_and_nl env =
712 | Tspace
| Tnewline
-> skip_spaces_and_nl env
715 let rec comment env =
716 right_n 1 env comment_loop
718 and comment_loop
env =
719 match comment_token env with
734 let rec line_comment env =
735 line_comment_loop
env
737 and line_comment_loop
env =
740 | Tnewline
-> back env
741 | Tspace
-> keep_space env; line_comment_loop
env
742 | _ -> last_token env; line_comment_loop
env
744 (*****************************************************************************)
745 (* Generic handling of newlines + spaces + comments.
747 * -) Newlines are removed
748 * -) Comments are preserved
749 * -) Spaces are removed
751 * There are some cases where we need to handle comments "by hand", but this
752 * logic is the one we want most of the time.
754 (*****************************************************************************)
756 let rec keep_comment env =
759 | Tspace
-> keep_comment env
765 if !(env.last
) <> Newline
then space env;
767 line_comment_loop
env;
772 let rec generic_nsc env =
773 match !(env.last_token) with
776 if !(env.last
) <> Newline
&& !(env.last
) <> Space
780 if attempt env is_closing_list
784 if !(env.last
) <> Newline
787 line_comment_loop
env;
797 and is_closing_list
env =
800 | Tspace
| Tnewline
-> is_closing_list
env
801 | Trp
| Trb
| Tgt
| Tcomma
| Trcb
| Tsc
-> true
804 (*****************************************************************************)
805 (* Wrappers for newlines, spaces and comments.
807 * Most of the time (not always), we want to look at the next "real" token, in
808 * other words: we want to skip white spaces and the comments to see what the
809 * next token looks like (and presumably decide what to do based on that).
811 (*****************************************************************************)
813 let rec wrap_non_ws env f
=
815 | Tnewline
| Tspace
->
819 let rec wrap_eof env f
=
821 | Tnewline
| Tspace
| Tline_comment
| Topen_comment
->
826 let rec wrap_eof_xhp env f
=
827 match xhp_token env with
828 | Tnewline
| Tspace
| Tline_comment
| Topen_comment
->
836 | Tnewline
| Tspace
| Tline_comment
| Topen_comment
->
841 let rec wrap_xhp env f
=
842 match xhp_token env with
844 | Tnewline
| Tspace
| Tline_comment
| Topen_comment
->
849 let wrap_word env f
= wrap env begin function
850 | Tword
-> f
!(env.last_str
)
854 let next_real_token_info ~
wrap env =
855 attempt env begin fun env ->
856 wrap env begin fun tok ->
857 let tok_str = !(env.last_str
) in
862 let next_token ?
(wrap=wrap_eof) env =
863 let tok, _tok_str
= next_real_token_info ~
wrap env in
866 let next_token_str ?
(wrap=wrap_eof) env =
867 let _tok, tok_str = next_real_token_info ~
wrap env in
870 let next_non_ws_token env =
871 attempt env begin fun env ->
872 wrap_non_ws env (fun tok -> tok)
875 (*****************************************************************************)
876 (* Helpers to look ahead. *)
877 (*****************************************************************************)
879 let try_words env wordl f
= wrap env begin function
880 | Tword
when List.mem wordl
!(env.last_str
) ->
885 let try_word env word f
= try_words env [word] f
887 let try_token env tok f
= wrap env begin function
888 | tok'
when tok = tok'
->
894 let opt_word word env = wrap env begin function
895 | Tword
when !(env.last_str
) = word ->
900 let opt_tok tok env = wrap env begin function
901 | tok'
when tok = tok'
->
906 (*****************************************************************************)
907 (* There are cases where the formatter expects a token (e.g. a semi colon).
908 * If the token is not found, the whole process stops, because one of the
909 * assumption of the formatter is that we are dealing with correct Hack code
910 * (at least for now ;-)).
912 * There is a debug mode (default turned to false) that gives a lot of context
913 * on where the error was found. It's handy to leave it here in case someone
914 * else wants to do some work with the formatter.
916 (*****************************************************************************)
920 let rec mycat n
env =
921 if n
< 0 then () else
925 let n = n - (String.length
!(env.last_str
)) in
926 Buffer.add_string env.buffer !(env.last_str
);
929 (* Used to give some context while debugging *)
930 let print_error tok_str env =
931 Buffer.add_string env.buffer !(env.last_str
);
932 Buffer.add_string env.buffer "<----";
934 let buffer = Buffer.contents
env.buffer in
936 if String.length
buffer > 400 then
937 String.sub
buffer (String.length
buffer - 400 -1) 400
941 (Pos.string (Pos.make
(env.file
:> string) env.lexbuf
))^
"\n"^
942 (Printf.sprintf
"Expected: %s, found: '%s'\n" tok_str !(env.last_str
))^
945 output_string stderr
error;
948 let expect tok_str env = wrap env begin fun _ ->
949 if !(env.last_str
) = tok_str
952 if debug then print_error tok_str env;
957 let expect_token tok env = wrap env begin fun x
->
965 let expect_xhp tok_str env = wrap_xhp env begin fun _ ->
966 if !(env.last_str
) = tok_str
970 print_error tok_str env;
977 (*****************************************************************************)
978 (* Helper functions to determine if a function has consumed tokens. *)
979 (*****************************************************************************)
981 let consume_value env f
=
982 let pos_before = get_pos env in
983 let f_return = f
env in
984 let pos_after = get_pos env in
985 let has_consumed = pos_before <> pos_after in
986 has_consumed, f_return
988 let has_consumed env f
=
989 let result, _f_value
= consume_value env f
in
992 let is_followed_by env f
tok_str =
993 attempt env begin fun env ->
994 has_consumed env f
&&
995 next_token_str env = tok_str
998 let wrap_would_consume env f
=
999 attempt env begin fun env ->
1000 wrap_eof env begin fun _ ->
1006 (*****************************************************************************)
1007 (* Logic preserving newlines. *)
1008 (*****************************************************************************)
1010 let empty_line env =
1011 let tok = ref Tspace
in
1012 while !tok = Tspace
do tok := token env done;
1015 | _ -> back env; false
1017 let is_empty_line env =
1018 attempt env empty_line
1020 let rec preserve_nl_space env =
1021 match token env with
1024 preserve_nl_space env
1026 while is_empty_line env do
1027 ignore
(empty_line env)
1034 let rec preserve_nl env f
=
1035 match token env with
1038 assert (token env = Tnewline
);
1039 preserve_nl_space env;
1046 | Tspace
when is_empty_line env ->
1047 preserve_nl_space env;
1052 preserve_nl_space env;
1060 (*****************************************************************************)
1061 (* Dealing with lists. *)
1062 (*****************************************************************************)
1064 let rec list env element
= preserve_nl env begin fun env ->
1065 if has_consumed env element
1066 then (newline env; add_block_tag env; list env element
)
1069 (*****************************************************************************)
1070 (* List comma separated. *)
1071 (*****************************************************************************)
1073 let rec list_comma_loop :
1074 'a
. break
:(env -> unit) -> ('a
-> env -> 'a
) -> 'a
-> env -> 'a
=
1075 fun ~break element acc
env ->
1076 while has_consumed env begin fun env ->
1077 wrap env begin function
1078 | Topen_comment
| Tline_comment
-> generic_nsc env
1083 let has_consumed, acc
= consume_value {
1084 env with char_break
= env.char_break
- 1
1087 then list_comma_loop_remain ~break element acc
env
1090 and list_comma_loop_remain
:
1091 'a
. break
:(env -> unit) -> ('a
-> env -> 'a
) -> 'a
-> env -> 'a
=
1092 fun ~break element acc
env ->
1093 wrap_eof env begin function
1096 let continue = wrap_would_consume env (element acc
) in
1099 seq env [last_token; comment_after_comma ~break
; break
];
1100 list_comma_loop ~break element acc
env
1108 and comment_after_comma ~break
env =
1109 match token env with
1112 if attempt env begin fun env ->
1129 line_comment_loop
env;
1132 comment_after_comma ~break
env
1139 let rec list_comma_single_comment env =
1140 let k = list_comma_single_comment in
1141 match token env with
1143 | Tspace
| Tnewline
-> k env
1150 let list_comma_single element
env =
1151 list_comma_single_comment env;
1152 let f () env = element
env in
1153 let () = list_comma_loop ~break
:space f () env in
1156 let list_comma_multi_maybe_trail ~trailing element
env =
1157 let trailing = trailing && not
env.no_trailing_commas
in
1158 let break = newline in
1159 let trailing = list_comma_loop ~
break element
trailing env in
1160 if trailing && !(env.last
) <> Newline
1161 then seq env [out ","; comment_after_comma ~
break]
1163 let list_comma_multi ~
trailing element
env =
1164 let f acc
env = ignore
(element
env); acc
in
1165 list_comma_multi_maybe_trail ~
trailing f env
1167 let list_comma_multi_nl ~
trailing element
env =
1169 list_comma_multi ~
trailing element
env;
1172 let list_comma ?
(trailing=true) element
env =
1174 (list_comma_single element
)
1175 (list_comma_multi ~
trailing element
)
1177 let list_comma_nl ?
(trailing=true) element
env =
1179 (list_comma_single element
)
1180 (list_comma_multi_nl ~
trailing element
)
1182 (*****************************************************************************)
1183 (* Semi colons are special because we want to keep the comments on the same
1184 * line right after them.
1186 (*****************************************************************************)
1188 let semi_colon env =
1189 expect ";" env; space env; keep_comment env
1191 (*****************************************************************************)
1192 (* The entry point *)
1193 (*****************************************************************************)
1197 | Parsing_error
of Errors.error list
1201 let rec entry ~keep_source_metadata ~no_trailing_commas ~modes
1202 (file
: Path.t
) from to_ content
k =
1204 let errorl, () = Errors.do_
begin fun () ->
1205 let rp = Relative_path.(create Dummy
(file
:> string)) in
1206 let {Parser_hack.file_mode
; _} =
1207 Parser_hack.program
rp content
in
1208 if not
(List.mem modes file_mode
) then raise PHP
;
1211 then Parsing_error
errorl
1213 let lb = Lexing.from_string content
in
1214 let env = empty file
lb from to_ keep_source_metadata no_trailing_commas
in
1219 | PHP
-> Disabled_mode
1221 Printexc.print_backtrace stderr
;
1224 (*****************************************************************************)
1225 (* Hack header <?hh *)
1226 (*****************************************************************************)
1228 and header
env = wrap env begin function
1230 seq env [last_token; mode
; newline];
1231 stmt_list ~is_toplevel
:true env
1236 match token env with
1237 | Tspace
-> mode
env
1239 space env; last_token env;
1244 (*****************************************************************************)
1246 (*****************************************************************************)
1249 match token env with
1251 | Tnewline
| Tspace
| Tline_comment
| Topen_comment
->
1259 match token env with
1260 (* names can contain colons, but cannot end with them *)
1261 | Tcolon
when attempt env begin fun env ->
1262 match token env with
1268 | Tpercent
| Tminus
| Tword
| Tbslash
->
1274 (*****************************************************************************)
1276 (*****************************************************************************)
1278 and shape_type_elt
env =
1279 if has_consumed env expr
1280 then seq env [space; expect "=>"; space; hint
]
1282 (*****************************************************************************)
1284 (*****************************************************************************)
1288 if attempt env begin fun env ->
1290 next_token env = Teq
1293 else (space env; hint
env);
1299 if attempt env begin fun env ->
1301 next_token env = Tsc
1304 else (space env; hint
env);
1305 seq env [space ; name
; expect ";"]
1307 (*****************************************************************************)
1308 (* Type Constants *)
1309 (*****************************************************************************)
1311 and is_typeconst
env =
1312 attempt env begin fun env ->
1313 wrap_non_ws env begin function
1314 | Tword
when !(env.last_str
) = "type" ->
1315 (match next_non_ws_token env with
1316 | Teq
| Tsc
-> false
1323 and type_const
env =
1324 seq env [last_token; space; expect "type"; space; hint
; as_constraint
];
1325 if next_non_ws_token env = Teq
1326 then seq env [space; expect "="; space; hint
; semi_colon]
1329 and abs_type_const
env =
1330 seq env [last_token; space; expect "type"; space; hint
];
1331 if attempt env begin fun env ->
1333 next_token env = Tsc
1335 then seq env [as_constraint
; semi_colon]
1338 (*****************************************************************************)
1340 (*****************************************************************************)
1342 and hint_function_params
env =
1344 list_comma ~
trailing:true hint_function_param
env;
1347 and hint_function_param
env = wrap env begin function
1348 | Tellipsis
-> last_token env
1349 | _ -> back env; hint
env
1352 and taccess_loop
env = wrap env begin function
1353 | Tcolcol
when next_token env = Tword
->
1355 wrap env begin function
1364 and hint
env = wrap env begin function
1365 | Tplus
| Tminus
| Tqm
| Tat
| Tbslash
| Tpipe
->
1368 | Tpercent
| Tcolon
->
1373 | Tword
when !(env.last_str
) = "shape" ->
1376 if next_token env = Trp
|| attempt env begin fun env ->
1377 (* does the shape have only one element? *)
1379 wrap_eof env begin function
1380 | Tcomma
-> next_token env = Trp
1385 list_comma_single shape_type_elt
env
1387 right env (list_comma_multi_nl ~
trailing:true shape_type_elt
);
1393 typevar_constraint
env;
1397 (match token env with
1398 | Tword
when !(env.last_str
) = "function" ->
1400 hint_function_params
env;
1411 and typevar_constraint
env =
1412 try_words env ["as"; "super"] begin fun env ->
1419 and as_constraint
env =
1420 try_word env "as" begin fun env ->
1427 and hint_parameter
env = wrap env begin function
1430 hint_list ~
trailing:false env;
1435 and hint_list ?
(trailing=true) env =
1436 list_comma ~
trailing:trailing hint
env
1438 (*****************************************************************************)
1440 (*****************************************************************************)
1443 seq env [expect_token Tword
; hint_parameter
; space];
1444 try_token env Tcolon
(seq_fun
1445 [last_token; space; hint
; as_constraint
; space]);
1446 (* stmt parses any list of statements, including things like $x = 1; which
1447 * are not valid in an enum body, but since we run the parser before
1448 * formatting the text, we can be sure tha we only encounter valid enum body
1449 * statements at this point. *)
1450 stmt ~is_toplevel
:false env
1452 (*****************************************************************************)
1454 (*****************************************************************************)
1457 seq env [opt_tok Tamp
; name
; hint_parameter
];
1458 Try.one_line env fun_signature_single fun_signature_multi
;
1459 if next_token env = Tlcb
1461 stmt ~is_toplevel
:false env
1463 (*****************************************************************************)
1464 (* function foo($arg1, $arg2 ...): return_type (all on one line) *)
1465 (*****************************************************************************)
1467 and fun_signature_single
env =
1469 right env (list_comma_single (ignore_ fun_param
));
1470 seq env [expect ")"; return_type
; use
]
1472 (*****************************************************************************)
1473 (* Multi line function signature (adds a trailing comma if missing, unless
1474 * the last param is variadic).
1480 * There is a special case with comments, when the only thing present is a
1481 * comment, we don't want to add a trailing comma.
1483 (*****************************************************************************)
1485 and fun_signature_multi
env =
1486 seq env [expect "("; newline];
1487 if next_token env = Trp
1488 then right env (fun env -> wrap env (fun _ -> back env))
1489 else right env fun_params_multi
;
1490 seq env [newline; expect ")"; return_type
; use
]
1492 and fun_params_multi
env = list_comma_multi_maybe_trail
1494 begin fun trailing env ->
1495 let is_variadic = fun_param
env in
1496 trailing && not
is_variadic
1501 let curr_pos = !(env.abs_pos
) in
1503 if !(env.abs_pos
) != curr_pos
1506 seq env [attribute
; space_opt; modifier_list
; space_opt; hint
; space_opt];
1508 let is_variadic = wrap_eof env begin function
1509 | Tellipsis
-> last_token env; true
1510 | _ -> back env; false
1513 seq env [opt_tok Tamp
; opt_tok Tellipsis
; opt_tok Tlvar
];
1514 try_token env Teq
(seq_fun [space; last_token; space; expr
]);
1517 and return_type
env =
1518 try_token env Tcolon
(seq_fun [last_token; space; hint
])
1520 (*****************************************************************************)
1522 (*****************************************************************************)
1525 seq env [name
; hint_parameter
; class_extends
; space; class_body
]
1527 (*****************************************************************************)
1528 (* Class extends/implements:
1529 * class ... extends A, B, C (on the same line)
1531 (*****************************************************************************)
1533 and class_extends_single
env =
1534 seq env [last_token; space; list_comma_single hint
]
1536 (*****************************************************************************)
1537 (* Class extends/implements:
1540 * extends A, B, C (on a different line)
1546 * A, B, C (on a different line)
1548 (*****************************************************************************)
1550 and nl_class_extends_single ~
break env =
1552 let line = !(env.line) in
1553 right env begin fun env ->
1556 right env begin fun env ->
1557 list_comma_single hint
env
1560 if line <> !(env.line) && env.report_fit
1561 then env.failed
:= 1
1563 (*****************************************************************************)
1564 (* Class extends/implements:
1571 (*****************************************************************************)
1573 and class_extends_multi
env =
1574 right env begin fun env ->
1578 right env begin fun env ->
1579 list_comma_multi ~
trailing:false hint
env
1583 and class_extends
env = wrap_word env begin function
1584 | "extends" | "implements" ->
1587 class_extends_single
1590 (nl_class_extends_single ~
break:space)
1593 (nl_class_extends_single ~
break:newline)
1602 and class_body
env =
1604 if next_non_ws_token env = Trcb
(* Empty class body *)
1609 right env begin fun env ->
1610 list env class_element
;
1616 and class_element
env = wrap env begin function
1621 class_element_word
env !(env.last_str
)
1625 expr_list ~
trailing:false { env with in_attr
= true };
1634 and class_element_word
env = function
1636 seq env [space; last_token; space; fun_
; newline]
1637 | "public" | "protected" | "private" | "abstract"
1638 | "final"| "static" | "async" ->
1640 seq env [modifier_list
; after_modifier
; newline]
1646 seq env [last_token; space; class_extends
; semi_colon]
1649 [last_token; space; hint_list ~
trailing:false; semi_colon; newline]
1651 seq env [last_token; xhp_category
; semi_colon]
1654 xhp_class_attribute_list
env
1663 and modifier_list
env =
1664 let pos_before = get_pos env in
1666 let pos_after = get_pos env in
1667 if pos_before = pos_after then () else begin
1672 and modifier
env = try_token env Tword
begin fun env ->
1673 match !(env.last_str
) with
1674 | "public" | "protected" | "private" | "abstract"
1675 | "final"| "static" | "async" ->
1680 and attribute
env = try_token env Tltlt
begin fun env ->
1682 expr_list ~
trailing:false { env with in_attr
= true };
1687 and use
env = try_word env "use" begin fun env ->
1688 seq env [space; last_token; space; expect "("; expr_list
; expect ")"]
1691 and after_modifier
env = wrap env begin function
1692 | Tword
when !(env.last_str
) = "const" ->
1694 then abs_type_const
env
1696 | Tword
when !(env.last_str
) = "function" ->
1697 seq env [last_token; space; fun_
]
1704 and class_members
env = class_members_list class_member
env
1706 and class_members_list member_handler
env =
1708 (class_member_list_single member_handler
)
1709 (fun env -> right env (class_member_list_multi member_handler
));
1712 and class_member_list_single member_handler
env =
1714 list_comma_single member_handler
env
1716 and class_member_list_multi member_handler
env =
1718 list_comma_multi ~
trailing:false member_handler
env
1720 and class_member
env = wrap env begin function
1721 | Tword
(* In case we are dealing with a constant *)
1725 (seq_fun [space; last_token; space; expr
])
1730 (*****************************************************************************)
1731 (* XHP formatting *)
1732 (*****************************************************************************)
1734 and xhp_children
env = wrap env begin function
1737 right env (list_comma_nl ~
trailing:false xhp_children
);
1739 xhp_children_post
env;
1740 xhp_children_remain
env
1744 xhp_children_post
env;
1745 xhp_children_remain
env
1748 and xhp_children_post
env = wrap env begin function
1749 | Tplus
| Tqm
| Tstar
->
1754 and xhp_children_remain
env = wrap env begin function
1756 seq env [space; last_token; space; xhp_children
]
1760 and xhp_category
env =
1761 space env; list_comma ~
trailing:false name
env
1763 and xhp_class_attribute_list
env =
1765 (class_member_list_single xhp_class_attribute
)
1766 (fun env -> newline env; right env xhp_class_attribute_list_multi
);
1769 and xhp_class_attribute_list_multi
env = preserve_nl env begin fun env ->
1770 xhp_class_attribute
env;
1771 match token env with
1773 seq env [last_token; keep_comment; newline; add_block_tag];
1774 xhp_class_attribute_list_multi
env
1778 and xhp_class_attribute
env =
1780 (xhp_class_attribute_impl ~enum_list_elts
:(list_comma_single expr
))
1781 (xhp_class_attribute_impl ~enum_list_elts
:
1782 (fun env -> right env (list_comma_multi_nl ~
trailing:false expr
)))
1784 and xhp_class_attribute_impl ~enum_list_elts
env =
1785 let curr_pos = !(env.abs_pos
) in
1786 wrap env begin function
1787 | Tword
when !(env.last_str
) = "enum" ->
1793 | _ -> back env; hint
env
1795 if !(env.abs_pos
) != curr_pos then begin
1796 match next_token env with
1797 | Tsc
| Tcomma
-> ()
1801 wrap env begin function
1802 | Teq
-> seq env [space; last_token; space; expr
]
1805 (match next_token env with
1806 | Tsc
| Tcomma
-> ()
1810 (*****************************************************************************)
1812 (*****************************************************************************)
1815 attempt env begin fun env ->
1816 match token env with
1817 | Tpercent
| Tcolon
| Tword
->
1819 wrap_eof_xhp env begin function
1820 | Tgt
| Tword
| Tslash
-> true
1827 and xhp_tag_kind
env =
1828 attempt env begin fun env ->
1831 xhp_attribute_list ~
break:space env;
1832 match next_token ~
wrap:wrap_eof_xhp env with
1833 | Tslash
-> `Xhp_self_closing
1834 | Tgt
-> `Xhp_paired
1835 | _ -> raise Format_error
1838 (* First we try inserting the XHP on the current line, e.g.
1840 * $foo = <aaaaaaaaaa>1</aaaaaaaaaa>;
1842 * If that fails to fit within char_break, then we try inserting the same XHP
1846 * <aaaaaaaaaa>1</aaaaaaaaaa>;
1848 * If that *still* fails to fit, we split the XHP up into multiple lines:
1856 match xhp_tag_kind
env with
1857 | `Xhp_self_closing
->
1859 xhp_self_closing_single
1863 xhp_self_closing_single
1864 xhp_self_closing_multi
)
1874 and xhp_self_closing_single
env =
1875 seq env [expect_xhp "<"; name
; xhp_attribute_list ~
break:space];
1876 seq env [space; expect_xhp "/"; expect_xhp ">"];
1878 and xhp_self_closing_multi
env =
1879 seq env [expect_xhp "<"; name
];
1880 right env (xhp_attribute_list ~
break:newline);
1881 seq env [newline; expect_xhp "/"; expect_xhp ">"];
1884 and xhp_paired_single
env =
1885 seq env [expect_xhp "<"; name
; xhp_attribute_list ~
break:space];
1886 seq env [expect_xhp ">"; skip_spaces_and_nl; xhp_body
];
1890 and xhp_paired_multi
env =
1892 let margin_pos = !(env.char_pos
) in
1896 xhp_attribute_list ~
break:space env;
1900 margin_set margin_pos env
1901 (xhp_attribute_list ~
break:newline);
1905 skip_spaces_and_nl env;
1906 margin_set margin_pos env begin fun env ->
1913 and xhp_multi_post
env =
1914 match xhp_token env with
1915 | Tnewline
| Tspace
->
1917 | Tlt
when is_xhp
env ->
1922 and xhp_close_tag
env =
1923 seq env [expect_xhp "<"; expect_xhp "/"; name
; expect_xhp ">"]
1925 and xhp_attribute_list ~
break env = wrap_xhp env begin function
1930 xhp_attribute_assign
1934 right env xhp_attribute_value
1936 xhp_attribute_list ~
break env
1941 and xhp_attribute_assign
env =
1943 xhp_attribute_value
env
1945 and xhp_attribute_value
env = wrap_xhp env begin function
1946 | Tquote
| Tdquote
as tok ->
1948 string ~last
:tok env
1957 (* It seems like whitespace is significant in XHP, but only insofar as it acts
1958 * as a separator of non-whitespace characters. That is, consecutive whitespace
1959 * will be rendered as a single space at runtime. Thus the handling of xhp_body
1960 * has to be slightly different from the rest of the syntax, which does not
1961 * treat whitespace as significant. In particular, we output consecutive
1962 * whitespace here as a single space, unless we have just wrapped a line, in
1963 * which case we output the necessary number of spaces required by the
1964 * indentation level. *)
1967 match xhp_token env with
1969 | Tnewline
| Tspace
->
1970 if !(env.last
) <> Newline
then space env;
1972 | Topen_xhp_comment
->
1976 | Tlt
when is_xhp
env ->
1979 xhp_keep_one_nl
env;
1996 xhp_keep_one_nl
env;
1999 let pos = !(env.char_pos
) in
2000 let text = xhp_text
env (Buffer.create
256) x
in
2001 if pos + String.length
text >= env.char_size
2003 out text { env with report_fit
= false };
2007 (* preserves up to one empty line between XHP blocks *)
2008 and xhp_keep_one_nl
env =
2009 match xhp_token env with
2013 (match xhp_token env with
2014 | Tnewline
-> force_nl env
2016 while xhp_token env = Tnewline
do () done;
2021 and xhp_text
env buf
= function
2022 | Tnewline
| Tspace
| Tlt
| Tlcb
| Teof
| Tclose_xhp_comment
->
2026 Buffer.add_string buf
!(env.last_out
);
2027 xhp_text
env buf
(xhp_token env)
2029 and xhp_comment
env = Try.one_line env xhp_comment_single xhp_comment_multi
2031 and xhp_comment_single
env =
2032 seq env [xhp_comment_body
; expect_xhp "-->"]
2034 and xhp_comment_multi
env =
2036 right env xhp_comment_body
;
2038 expect_xhp "-->" env
2040 and xhp_comment_body
env =
2041 match xhp_token env with
2043 | Tnewline
| Tspace
->
2044 if !(env.last
) <> Newline
then space env;
2045 xhp_comment_body
env
2046 | Tclose_xhp_comment
->
2050 xhp_comment_body
env
2052 let pos = !(env.char_pos
) in
2053 let text = xhp_text
env (Buffer.create
256) x
in
2054 if pos + String.length
text >= env.char_size
2058 xhp_comment_body
env
2060 (*****************************************************************************)
2062 (*****************************************************************************)
2064 and stmt ~is_toplevel
env = wrap env begin function
2066 line { env with in_attr
= true }
2067 [last_token; expr_list ~
trailing:false; expect ">"; expect ">"];
2068 stmt ~is_toplevel
env
2070 let word = !(env.last_str
) in
2071 stmt_word ~is_toplevel
env word
2074 if next_non_ws_token env <> Trcb
then begin
2075 seq env [space; keep_comment; newline];
2077 right env (stmt_list ~is_toplevel
);
2081 seq env [last_token; space; keep_comment; newline]
2084 if has_consumed env expr
2088 and stmt_word ~is_toplevel
env word =
2090 | "type" | "newtype" | "namespace" | "use"
2091 | "abstract" | "final" | "interface" | "const"
2092 | "class" | "trait" | "function" | "async" | "enum" as word ->
2094 then stmt_toplevel_word
env word
2096 | "public" | "protected" | "private" | "case" | "default" ->
2099 | "require" | "require_once" | "include" | "include_once" ->
2100 seq env [last_token; space];
2101 right env (list_comma_nl ~
trailing:false expr
);
2104 seq env [last_token; space; expr
; semi_colon]
2105 | "break" | "continue" | "return" ->
2107 if wrap_would_consume env expr
2108 then rhs_assign
env;
2110 | "static" when next_token env <> Tcolcol
->
2111 seq env [last_token; space];
2113 (seq_fun [space; list_comma_single expr
])
2114 (seq_fun [newline; right_fun (list_comma_multi ~
trailing:false expr
)]);
2118 if_ ~is_toplevel
env
2120 seq env [last_token; block
];
2121 seq env [space; expect "while"; space; expr_paren
; opt_tok Tsc
]
2123 seq env [last_token; space; expr_paren
; block
; newline]
2134 seq env [last_token; space; block
; space];
2138 seq env [expr
; semi_colon]
2140 and stmt_toplevel_word
env = function
2141 | "abstract" | "final" | "async" ->
2142 seq env [last_token; space; stmt ~is_toplevel
:true]
2143 | "interface" | "class" | "trait" ->
2144 seq env [last_token; space; class_
]
2146 seq env [last_token; space; enum_
]
2148 seq env [last_token; space; fun_
]
2151 | "type" | "newtype" ->
2152 seq env [last_token; space; hint
; as_constraint
; space;
2161 namespace_use_list
env;
2165 and stmt_list ~is_toplevel
env =
2166 (* -1 for the trailing semicolon *)
2167 let env = {env with char_break
= min
env.char_break
(env.char_size
- 1)} in
2168 list env (stmt ~is_toplevel
)
2170 and block ?
(is_toplevel
=false) env = wrap env begin function
2172 seq env [space; last_token; space; keep_comment; newline];
2174 right env (stmt_list ~is_toplevel
);
2179 right env (stmt ~is_toplevel
)
2182 (*****************************************************************************)
2184 (*****************************************************************************)
2186 and if_ ~is_toplevel
env =
2187 seq env [space; expr_paren
; block ~is_toplevel
; else_ ~is_toplevel
]
2189 and else_ ~is_toplevel
env =
2190 match next_token_str env with
2191 | "else" | "elseif" ->
2193 else_word ~is_toplevel
env;
2194 else_ ~is_toplevel
env
2197 and else_word ~is_toplevel
env = wrap_word env begin function
2199 seq env [last_token; space];
2200 wrap_word env (function
2201 | "if" -> seq env [last_token; space; expr_paren
; space]
2203 block ~is_toplevel
env;
2205 seq env [out "else"; space; out "if"; space; expr_paren
; space];
2206 block ~is_toplevel
env;
2210 (*****************************************************************************)
2212 (*****************************************************************************)
2215 seq env [space; name
];
2216 wrap env begin function
2217 | Tsc
-> back env; semi_colon env;
2219 space env; last_token env; newline env;
2220 right env (stmt_list ~is_toplevel
:true);
2226 and namespace_use_list
env =
2227 seq env [space; opt_word "const"; opt_word "function"; space;];
2228 let is_group_use = attempt env begin fun env ->
2230 match next_token_str env with
2234 if is_group_use then seq env [name
; expect "{"];
2235 right env (list_comma_nl ~
trailing:false namespace_use
);
2237 if is_group_use then [expect "}"; semi_colon;]
2238 else [semi_colon] in
2241 and namespace_use
env =
2242 let next = match next_token_str env with
2244 | "function" as x
-> [opt_word x
; space; name
;]
2247 if next_token_str env = "as" then seq env [space; expect "as"; space; name
;];
2250 (*****************************************************************************)
2252 (*****************************************************************************)
2255 seq env [space; expect "("];
2256 margin_set (!(env.char_pos
) - 1) env foreach_as
;
2261 and foreach_as
env =
2262 seq env [expr
; space; opt_word "await"; space; expect "as"];
2264 (fun env -> seq env [space; expr
; arrow_opt
])
2265 (fun env -> seq env [newline; expr
; arrow_opt
])
2267 (*****************************************************************************)
2269 (*****************************************************************************)
2272 seq env [space; expect "("];
2273 (* the expr_list at toplevel adds newlines before and after the list, which
2275 let expr_list = list_comma ~
trailing:false expr
in
2276 let for_exprs ~
break = begin fun env ->
2277 seq env [expr_list; semi_colon];
2278 seq env [break; expr_list; semi_colon];
2279 seq env [break; expr_list]
2282 (for_exprs ~
break:space)
2285 right env (for_exprs ~
break:newline);
2288 seq env [expect ")"; block
; newline]
2290 (*****************************************************************************)
2291 (* Switch statement *)
2292 (*****************************************************************************)
2295 seq env [space; expr_paren
; space];
2296 line env [expect "{"];
2299 line env [expect "}"]
2302 right env begin fun env ->
2307 wrap env begin function
2311 case_word
env !(env.last_str
)
2315 and case_word
env = function
2317 seq env [last_token; space; expr
; expect ":"; space; keep_comment;
2319 right env (stmt_list ~is_toplevel
:false)
2321 seq env [last_token; expect ":"; keep_comment; newline];
2322 right env (stmt_list ~is_toplevel
:false)
2326 and catch_list
env = wrap_word env begin function
2330 (match next_token_str env with
2331 | "catch" | "finally" ->
2332 space env; catch_list
env
2341 and catch_remain
env =
2342 seq env [space; expect "("; (ignore_ fun_param
); expect ")"; block
]
2344 (*****************************************************************************)
2346 (*****************************************************************************)
2348 and rhs_assign
env =
2349 wrap env begin function
2357 (fun env -> space env; expr
env)
2358 (fun env -> newline env; right env expr
)
2360 !(env.last_str
) = "array" || !(env.last_str
) = "shape" ||
2361 !(env.last_str
) = "tuple" ->
2364 | Tword
when next_token env = Tlcb
->
2371 let line = !(env.line) in
2373 let lowest_pri = expr_lowest
env in
2374 if lowest_pri > 0 &&
2375 (lowest_pri != tarrow_prec
2376 || lowest_pri != tpipe_prec) &&
2378 then env.failed
:= 1;
2386 and expr_paren
env =
2388 (* an expr_paren is usually followed by `) {`, so take that into account *)
2389 let env = {env with char_break
= min
env.char_break
(env.char_size
- 3)} in
2390 margin_set (!(env.char_pos
) - 1) env expr
;
2393 and expr_break_tarrow
env =
2394 let env = { env with break_on
= tarrow_prec } in
2396 right env (fun env -> ignore
(expr_remain_loop
0 env))
2398 and expr_break_tpipe
env =
2399 let env = { env with break_on
= tpipe_prec } in
2401 right env (fun env -> ignore
(expr_remain_loop
0 env))
2404 let break_on = ref 0 in
2405 (** We try to shove as much of an expression as possible onto one line. If
2406 * that succeeds, good. If not, we add a linebreak at the lowest-priority
2407 * operator encountered. This ensures that things of higher priority
2408 * are kept on the same line.*)
2411 let line = !(env.line) in
2412 let lowest = expr_lowest
env in
2414 if !(env.failed
) <= 0 && line <> !(env.line) && lowest > 0
2415 then env.failed
:= max
1 (max
!(env.failed
) env.try_depth
);
2418 (** The linebreak is inserted by setting "break_on" in the env and
2419 * trying the expression output again. When the operator with that
2420 * "break_on" priority is encountered, that's when the break will be
2422 let break_on = !break_on in
2423 if break_on = tarrow_prec (* Operator -> is special *)
2424 then keep_best env ignore_expr_lowest expr_break_tarrow
2425 else if break_on = tpipe_prec (* Operator |> is special *)
2426 then keep_best env ignore_expr_lowest expr_break_tpipe
2427 else ignore_expr_lowest
{ env with break_on };
2431 and ignore_expr_lowest
env =
2432 ignore
(expr_lowest
env)
2434 and expr_lowest
env =
2435 let env = reset_priority env in
2437 expr_remain_loop
0 env
2439 and expr_remain_loop
lowest env =
2440 let pos_before = get_pos env in
2441 let lowest = expr_remain
lowest env in
2442 let pos_after = get_pos env in
2443 if pos_before = pos_after
2445 else expr_remain_loop
lowest env
2447 and expr_list ?
(trailing=true) env =
2448 list_comma_nl ~
trailing expr
{ env with break_on = 0; priority
= 0 }
2450 and expr_binop
lowest str_op op
env =
2451 with_priority env op
begin fun env ->
2454 if env.priority
= env.break_on
2459 if lowest = 0 then env.priority
else
2460 if env.priority
= 0 then lowest
2461 else min
env.priority
lowest in
2462 expr_remain_loop
lowest env
2465 and expr_binop_arrow
lowest str_op
tok env =
2466 with_priority env tok begin fun env ->
2467 if env.priority
= env.break_on
2469 seq env [newline; out str_op
];
2471 else out str_op
env;
2472 wrap env begin function
2475 | Tlcb
-> (* $xx->{...} *)
2484 if lowest = 0 then env.priority
else
2485 min
env.priority
lowest in
2486 expr_remain_loop
lowest env
2489 (** The pipe expression may have linebreaks inserted because that is the
2490 * operator precedence we are currently adding linebreaks on
2491 * (see "env.break_on"), or it may preserve linebreaks from the input that
2492 * are there for "prettiness" (i.e. not to satisfy the 80-char width limit.) *)
2493 and expr_binop_pipe
lowest str_op
tok env =
2494 with_priority env tok begin fun env ->
2495 let pretty_newline = (!(env.input_line_non_ws_token_count
) = 1)
2496 && (env.priority
!= env.break_on) in
2499 (** The pipe is the first non-ws token consumed from this line of input.
2500 * Allow it to be on a newline. *)
2501 right env begin function env ->
2502 let env = { env with break_on = tpipe_prec } in
2503 seq env [newline; out str_op
; space];
2505 expr_remain_loop
0 env
2509 if (env.priority
= env.break_on)
2510 then seq env [newline; out str_op
; space]
2511 else seq env [space; out str_op
; space];
2512 wrap env begin function
2518 if lowest = 0 then env.priority
else
2519 min
env.priority
lowest in
2520 expr_remain_loop
lowest env
2524 and expr_binop_dot
lowest str_op
env =
2525 with_priority env Tdot
begin fun env ->
2527 if env.priority
= env.break_on
2529 (match next_token env with
2530 | Tminus
| Tplus
| Tint
| Tfloat
-> space env
2534 if lowest = 0 then env.priority
else
2535 min
env.priority
lowest in
2536 expr_remain_loop
lowest env
2539 and expr_remain
lowest env =
2540 let tok = token env in
2541 let tok_str = !(env.last_out
) in
2544 seq env [space; last_token; comment];
2545 expr_remain
lowest env
2547 seq env [space; last_token; line_comment; newline];
2548 expr_remain
lowest env
2549 | Tnewline
| Tspace
->
2550 expr_remain
lowest env
2551 | Tplus
| Tminus
| Tstar
| Tslash
| Tstarstar
2552 | Teqeqeq
| Tpercent
2553 | Teqeq
| Tampamp
| Tbarbar
2554 | Tdiff
| Tlt
| Tdiff2
| Tgte
2555 | Tlte
| Tamp
| Tbar
| Tltlt
2556 | Tgtgt
| Txor
as op
->
2557 expr_binop
lowest tok_str op
env
2559 expr_binop_pipe
lowest tok_str tok env
2561 expr_binop_dot
lowest tok_str env
2562 | Tarrow
| Tnsarrow
->
2563 expr_binop_arrow
lowest tok_str tok env
2564 | Tgt
when env.in_attr
->
2571 if next_token env = Tlcb
2576 (match token env with
2578 expr_binop
lowest ">>" Tgtgt
env
2581 expr_binop
lowest ">" Tgt
env
2583 | Teq
| Tbareq
| Tpluseq
| Tstareq
| Tslasheq
2584 | Tdoteq
| Tminuseq
| Tpercenteq
| Txoreq
2585 | Tampeq
| Tlshifteq
| Trshifteq
->
2599 let env = { env with break_on = 0 } in
2605 (match token env with
2606 | Trb
-> last_token env
2607 | _ -> back env; expr
env; expect "]" env
2610 | Tqm
when attempt env begin fun env ->
2611 wrap_eof env begin function
2617 seq env [space; out "?"; expect ":"; space; expr
];
2623 (* Horrible Hack. We pretend the ternary operator is a binary operator
2624 * to make sure we get a new line after an assignment.
2625 * Without this hack, we could have results looking like this:
2631 expr_binop
lowest "??" Tqmqm
env
2632 | Tword
when !(env.last_str
) = "xor" ->
2633 expr_binop
lowest "xor" Txor
env
2634 | Tword
when !(env.last_str
) = "instanceof" ->
2644 and expr_atomic
env =
2645 let last = !(env.last_token) in
2646 let token = token env in
2649 seq env [last_token; line_comment; newline; expr_atomic
]
2651 seq env [last_token; comment; space; expr_atomic
]
2652 | Tnewline
| Tspace
->
2656 (match next_token env with
2657 | Tarrow
| Tnsarrow
as tok ->
2659 | Tarrow
-> expect "->" env
2660 | Tnsarrow
-> expect "?->" env
2661 | _ -> assert false);
2662 wrap env begin function
2677 if next_token env = Tdot
2679 | Tquote
| Tdquote
as tok ->
2681 string ~
last:tok env
2685 | Tamp
| Tat
| Tbslash
2686 | Tem
| Tincr
| Tdecr
| Ttild
| Tplus
| Tminus
->
2690 let word = !(env.last_str
) in
2691 expr_atomic_word
env last (String.lowercase
word)
2696 right env array_body
;
2699 let env = { env with break_on = 0 } in
2701 if is_followed_by env name
")"
2703 seq env [last_token; out_next; expect ")"; space; expr
]
2705 else if next_token_str env = "new"
2707 seq env [last_token; expr
; expect ")"]
2709 (* Short lambda parameters *)
2710 else if attempt env begin fun env ->
2712 list_comma (ignore_ fun_param
) env;
2713 seq env [expect ")"; return_type
];
2714 wrap_eof env (fun tok -> tok = Tlambda
)
2715 with Format_error
-> false
2718 Try.one_line env fun_signature_single fun_signature_multi
;
2723 margin_set (!(env.char_pos
) -1) env begin fun env ->
2728 | Tlt
when is_xhp
env ->
2737 and expr_atomic_word
env last_tok
= function
2738 | "true" | "false" | "null" ->
2740 | "array" | "shape" | "tuple" as v
->
2743 right env array_body
;
2747 expect (token_to_string Tlb
) env;
2748 (** Dict body looks exactly like an array body. *)
2749 right env array_body
;
2750 expect (token_to_string Trb
) env;
2751 | "empty" | "unset" | "isset" as v
->
2753 arg_list ~
trailing:false env
2761 begin match next_token env with
2762 | Tlcb
-> stmt ~is_toplevel
:false env
2763 | _ -> expr_atomic
env
2765 | "function" when last_tok
<> Tarrow
&& last_tok
<> Tnsarrow
->
2767 if next_non_ws_token env <> Tlp
then space env;
2772 with_priority env Tawait expr
2776 with_priority env Tyield array_element_single
2780 with_priority env Tclone expr
2783 wrap env begin function
2788 if next_token env <> Trcb
2789 then right env array_body
;
2793 let _ = expr_remain
0 env in
2802 and expr_call_list ?
(trailing=true) env =
2803 let env = { env with break_on = 0; priority
= 0 } in
2804 list_comma_nl ~
trailing expr_call_elt
env
2806 and expr_call_elt
env = wrap env begin function
2807 | Tellipsis
-> seq env [last_token; expr
]
2808 | _ -> back env; expr
env
2811 (*****************************************************************************)
2812 (* Ternary operator ... ? ... : ... *)
2813 (*****************************************************************************)
2815 and ternary_one_line
env =
2816 seq env [space; last_token; space; expr
; space; expect ":"; space; expr
]
2818 and ternary_multi_line
env =
2819 right env begin fun env ->
2821 [newline; last_token; space; expr
; newline; expect ":"; space; expr
]
2825 (*****************************************************************************)
2827 (*****************************************************************************)
2829 and string ~
last env =
2830 match token env with
2832 | tok when tok = last -> last_token env
2833 | tok -> string_char
env tok; string ~
last env
2835 and string_char
env = function
2837 | Tbslash
-> last_token env; out_next env
2838 | Tnewline
-> force_nl env
2839 | Tspace
-> keep_space env
2840 | _ -> last_token env
2842 (*****************************************************************************)
2844 (*****************************************************************************)
2847 let env = { env with margin
= ref 0 } in
2848 (match token env with
2849 | Tspace
-> heredoc
env
2853 let abs_start = env.lexbuf
.Lexing.lex_curr_pos
in
2854 string ~
last:Tquote
env;
2855 let len = env.lexbuf
.Lexing.lex_curr_pos
- abs_start - 1 in
2856 let str_value = String.sub
env.lexbuf
.Lexing.lex_buffer
abs_start len in
2857 heredoc_loop
str_value env
2861 heredoc_loop
!(env.last_str
) env
2865 heredoc_loop
"EOT" env
2867 (match token env with
2869 | _ -> back env; newline env)
2871 and heredoc_loop close
env =
2872 match token env with
2876 if attempt env begin fun env ->
2877 token env = Tword
&&
2878 !(env.last_str
) = close
&&
2879 match token env with
2880 | Tsc
| Tnewline
-> true
2883 then (ignore
(token env); last_token env)
2884 else heredoc_loop close
env
2885 | Tspace
-> keep_space env; heredoc_loop close
env
2886 | _ -> last_token env; heredoc_loop close
env
2888 (*****************************************************************************)
2890 (*****************************************************************************)
2892 and array_body
env =
2897 and array_one_line
env =
2898 list_comma_single array_element_single
env
2900 and array_multi_line
env =
2901 list_comma_multi_nl ~
trailing:true array_element_multi
env
2903 and array_element_single
env =
2907 and array_element_multi
env = wrap env begin fun _ ->
2915 match token env with
2920 (fun env -> space env; expr
env)
2921 (fun env -> newline env; right env expr
)
2925 (*****************************************************************************)
2926 (* Argument lists *)
2927 (*****************************************************************************)
2929 and arg_list ?
(trailing=true) env =
2932 if next_token env <> Trp
2933 then right env (expr_call_list ~
trailing);
2936 (*****************************************************************************)
2937 (* The outside API *)
2938 (*****************************************************************************)
2940 let region modes file ~
start ~end_ content
=
2941 entry ~keep_source_metadata
:false file
start end_ content
2942 ~no_trailing_commas
:false ~modes
2943 (fun env -> Buffer.contents
env.buffer)
2945 let program ?no_trailing_commas
:(no_trailing_commas
= false) modes file
2947 entry ~keep_source_metadata
:false file
0 max_int content
2948 ~no_trailing_commas ~modes
2949 (fun env -> Buffer.contents
env.buffer)
2951 let program_with_source_metadata modes file content
=
2952 entry ~keep_source_metadata
:true file
0 max_int content
2953 ~no_trailing_commas
:false ~modes
begin
2955 Buffer.contents
env.buffer, List.rev
!(env.source_pos_l
)