Repository navigation
Expand file tree
/
Copy pathres_printer.ml
More file actions
6504 lines (6318 loc) · 218 KB
/
Copy pathres_printer.ml
File metadata and controls
6504 lines (6318 loc) · 218 KB
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
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
module Doc = Res_doc
module Comment_table = Res_comments_table
module Comment = Res_comment
module Token = Res_token
module Parens = Res_parens
module Parsetree_viewer = Res_parsetree_viewer
let default_print_width = 100
type callback_style =
(* regular arrow function, example: `let f = x => x + 1` *)
| NoCallback
(* `Thing.map(foo, (arg1, arg2) => MyModuleBlah.toList(argument))` *)
| FitsOnOneLine
(* Thing.map(longArgumet, veryLooooongArgument, (arg1, arg2) =>
* MyModuleBlah.toList(argument)
* )
*)
| ArgumentsFitOnOneLine
let add_parens doc =
Doc.group
(Doc.concat
[
Doc.lparen;
Doc.indent (Doc.concat [Doc.soft_line; doc]);
Doc.soft_line;
Doc.rparen;
])
(* Keep the delimiters on the expression's first and last lines. The parser
excludes parentheses from expression locations, so breaking just inside
them would create blank lines or move comments on the next formatting pass. *)
let add_statement_parens doc =
Doc.group (Doc.concat [Doc.lparen; doc; Doc.rparen])
(* Module expressions hug their parens, so a multi-line structure or
signature keeps its braces next to them: [(M: {...})] *)
let add_mod_expr_parens doc = Doc.concat [Doc.lparen; doc; Doc.rparen]
(* Whether a module expression's printed form starts with a doc comment:
doc comments print before other attributes, and an unparenthesized
constraint or application starts with its leftmost module *)
let rec mod_expr_starts_with_doc_comment (mod_expr : Parsetree.module_expr) =
match mod_expr.pmod_desc with
(* A functor's attributes print on its first parameter, an await's after
[await] *)
| Pmod_functor _ | Pmod_await _ -> false
| _
when List.exists Parsetree_viewer.is_doc_comment_attribute
mod_expr.pmod_attributes ->
true
| _ when Parsetree_viewer.mod_expr_has_attributes mod_expr -> false
| Pmod_constraint (inner, _) -> mod_expr_starts_with_doc_comment inner
| Pmod_apply _ ->
let _, call_expr = Parsetree_viewer.mod_expr_apply mod_expr in
(not (Parens.mod_apply_callee call_expr))
&& mod_expr_starts_with_doc_comment call_expr
| _ -> false
let add_braces ?(force_break = false) doc =
Doc.breakable_group ~force_break
(Doc.concat
[
Doc.lbrace;
Doc.indent (Doc.concat [Doc.soft_line; doc]);
Doc.soft_line;
Doc.rbrace;
])
let add_async doc = Doc.concat [Doc.text "async "; doc]
let has_inline_type_definitions type_declarations =
type_declarations
|> List.find_opt (fun (td : Parsetree.type_declaration) ->
td.ptype_origin = Inline_record_definition)
|> Option.is_some
let get_first_leading_comment tbl loc =
match Hashtbl.find tbl.Comment_table.leading loc with
| comment :: _ -> Some comment
| [] -> None
| exception Not_found -> None
(* Checks if `loc` has a leading line comment, i.e. `// comment above`*)
let has_leading_line_comment tbl loc =
match get_first_leading_comment tbl loc with
| Some comment -> Comment.is_single_line_comment comment
| None -> false
let has_trailing_single_line_comment tbl loc =
match Hashtbl.find_opt tbl.Comment_table.trailing loc with
| Some (comment :: _) -> Comment.is_single_line_comment comment
| _ -> false
let has_any_trailing_line_comment tbl loc =
match Hashtbl.find_opt tbl.Comment_table.trailing loc with
| Some comments -> List.exists Comment.is_single_line_comment comments
| None -> false
let has_comment_below tbl loc =
match Hashtbl.find tbl.Comment_table.trailing loc with
| comment :: _ ->
let comment_loc = Comment.loc comment in
comment_loc.Location.loc_start.pos_lnum > loc.Location.loc_end.pos_lnum
| [] -> false
| exception Not_found -> false
let has_comments_inside tbl loc =
match Hashtbl.find_opt tbl.Comment_table.inside loc with
| None -> false
| _ -> true
let has_trailing_comments tbl loc =
match Hashtbl.find_opt tbl.Comment_table.trailing loc with
| None -> false
| _ -> true
let has_leading_comments tbl loc =
match Hashtbl.find_opt tbl.Comment_table.leading loc with
| None -> false
| _ -> true
(* Compact layouts print expression comments but not comments on the full
* labeled argument. For unlabeled arguments, leading expression comments
* already work in the compact layout; forcing a different layout can make
* comments exposed by removing parameter parentheses unstable. *)
let argument_requires_regular_layout cmt_tbl ((lbl, arg) as argument) =
let loc = Parsetree_viewer.argument_loc argument in
let has_leading_label_comments =
match lbl with
| Asttypes.Nolabel -> false
| Labelled _ | Optional _ ->
has_leading_comments cmt_tbl loc
|| has_leading_comments cmt_tbl arg.Parsetree.pexp_loc
in
(* Trailing comments can escape compact layouts when the body breaks. *)
has_leading_label_comments
|| has_trailing_comments cmt_tbl loc
|| has_trailing_comments cmt_tbl arg.pexp_loc
let print_multiline_comment_content txt =
(* Turns
* |* first line
* * second line
* * third line *|
* Into
* |* first line
* * second line
* * third line *|
*
* What makes a comment suitable for this kind of indentation?
* -> multiple lines + every line starts with a star
*)
let rec indent_stars lines acc =
match lines with
| [] -> Doc.nil
| [last_line] ->
let line = String.trim last_line in
let doc = Doc.text (" " ^ line) in
let trailing_space = if line = "" then Doc.nil else Doc.space in
List.rev (trailing_space :: doc :: acc) |> Doc.concat
| line :: lines ->
let line = String.trim line in
if line != "" && String.unsafe_get line 0 == '*' then
let doc = Doc.text (" " ^ line) in
indent_stars lines (Doc.hard_line :: doc :: acc)
else
let trailing_space =
let len = String.length txt in
if len > 0 && String.unsafe_get txt (len - 1) = ' ' then Doc.space
else Doc.nil
in
let content = Comment.trim_spaces txt in
Doc.concat [Doc.text content; trailing_space]
in
let lines = String.split_on_char '\n' txt in
match lines with
| [] -> Doc.text "/* */"
| [line] ->
Doc.concat
[Doc.text "/* "; Doc.text (Comment.trim_spaces line); Doc.text " */"]
| first :: rest ->
(* The printer writes the line break, so drop the CRs of a CRLF one *)
let first =
let len = ref (String.length first) in
while !len > 0 && String.unsafe_get first (!len - 1) = '\r' do
decr len
done;
String.sub first 0 !len [@doesNotRaise]
in
let first_line = Comment.trim_spaces first in
Doc.concat
[
Doc.text "/*";
(match first_line with
| "" | "*" -> Doc.nil
| _ -> Doc.space);
indent_stars rest [Doc.hard_line; Doc.text first_line];
Doc.text "*/";
]
let print_trailing_comment (prev_loc : Location.t) (node_loc : Location.t)
comment =
let single_line = Comment.is_single_line_comment comment in
let content =
let txt = Comment.txt comment in
if single_line then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
in
let diff =
let cmt_start = (Comment.loc comment).loc_start in
cmt_start.pos_lnum - prev_loc.loc_end.pos_lnum
in
let is_below =
(Comment.loc comment).loc_start.pos_lnum > node_loc.loc_end.pos_lnum
in
if diff > 0 || is_below then
Doc.concat
[
Doc.break_parent;
Doc.line_suffix
(Doc.concat
[
Doc.hard_line;
(if diff > 1 then Doc.hard_line else Doc.nil);
content;
]);
]
else if not single_line then Doc.concat [Doc.space; content]
else Doc.line_suffix (Doc.concat [Doc.space; content])
let print_comment_content comment =
let txt = Comment.txt comment in
if Comment.is_single_line_comment comment then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
let print_leading_comment ?next_comment comment =
let single_line = Comment.is_single_line_comment comment in
let content = print_comment_content comment in
let separator =
Doc.concat
[
(if single_line then Doc.concat [Doc.hard_line; Doc.break_parent]
else Doc.nil);
(match next_comment with
| Some next ->
let next_loc = Comment.loc next in
let curr_loc = Comment.loc comment in
let diff =
next_loc.Location.loc_start.pos_lnum
- curr_loc.Location.loc_end.pos_lnum
in
let next_single_line = Comment.is_single_line_comment next in
if single_line && next_single_line then
if diff > 1 then Doc.hard_line else Doc.nil
else if single_line && not next_single_line then
if diff > 1 then Doc.hard_line else Doc.nil
else if diff > 1 then Doc.concat [Doc.hard_line; Doc.hard_line]
else if diff == 1 then Doc.hard_line
else Doc.space
| None -> Doc.nil);
]
in
Doc.concat [content; separator]
(* This function is used for printing comments inside an empty block *)
let print_comments_inside cmt_tbl loc =
let print_comment comment =
let single_line = Comment.is_single_line_comment comment in
let txt = Comment.txt comment in
if single_line then Doc.text ("//" ^ txt)
else print_multiline_comment_content txt
in
let force_break =
loc.Location.loc_start.pos_lnum <> loc.Location.loc_end.pos_lnum
in
let rec loop acc comments =
match comments with
| [] -> Doc.nil
| [comment] ->
let cmt_doc = print_comment comment in
let cmts_doc = Doc.concat (Doc.soft_line :: List.rev (cmt_doc :: acc)) in
let doc =
Doc.breakable_group ~force_break
(Doc.concat
[Doc.if_breaks (Doc.indent cmts_doc) cmts_doc; Doc.soft_line])
in
doc
| comment :: rest ->
let cmt_doc = Doc.concat [print_comment comment; Doc.line] in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find cmt_tbl.Comment_table.inside loc with
| exception Not_found -> Doc.nil
| comments ->
Hashtbl.remove cmt_tbl.inside loc;
loop [] comments
(* This function is used for printing comments inside an empty file *)
let print_comments_inside_file cmt_tbl =
let rec loop acc comments =
match comments with
| [] -> Doc.nil
| [comment] ->
(* Nothing follows the last comment: the line break after a line comment
would add an empty line to the final newline of the file. *)
let cmt_doc = print_comment_content comment in
let doc =
Doc.group (Doc.concat [Doc.concat (List.rev (cmt_doc :: acc))])
in
doc
| comment :: (next_comment :: _comments as rest) ->
let cmt_doc = print_leading_comment ~next_comment comment in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find cmt_tbl.Comment_table.inside Location.none with
| exception Not_found -> Doc.nil
| comments ->
Hashtbl.remove cmt_tbl.inside Location.none;
Doc.group (loop [] comments)
let print_leading_comments node tbl loc =
let rec loop acc comments =
match comments with
| [] -> node
| [comment] ->
let cmt_doc = print_leading_comment comment in
let diff =
loc.Location.loc_start.pos_lnum
- (Comment.loc comment).Location.loc_end.pos_lnum
in
let separator =
if Comment.is_single_line_comment comment then
if diff > 1 then Doc.hard_line else Doc.nil
else if diff == 0 then Doc.space
else if diff > 1 then Doc.concat [Doc.hard_line; Doc.hard_line]
else Doc.hard_line
in
let comments = Doc.concat (List.rev (separator :: cmt_doc :: acc)) in
let doc = Doc.group (Doc.concat [Doc.comment comments; node]) in
doc
| comment :: (next_comment :: _comments as rest) ->
let cmt_doc = print_leading_comment ~next_comment comment in
loop (cmt_doc :: acc) rest
in
match Hashtbl.find tbl loc with
| exception Not_found -> node
| comments ->
(* Remove comments from tbl: Some ast nodes have the same location.
* We only want to print comments once *)
Hashtbl.remove tbl loc;
loop [] comments
let print_trailing_comments node tbl loc =
let rec loop prev acc comments =
match comments with
| [] -> Doc.concat (List.rev acc)
| comment :: comments ->
let cmt_doc = print_trailing_comment prev loc comment in
loop (Comment.loc comment) (cmt_doc :: acc) comments
in
match Hashtbl.find tbl loc with
| exception Not_found -> node
| [] -> node
| _first :: _ as comments ->
(* Remove comments from tbl: Some ast nodes have the same location.
* We only want to print comments once *)
Hashtbl.remove tbl loc;
let cmts_doc = loop loc [] comments in
Doc.concat [node; cmts_doc]
let print_comments doc (tbl : Comment_table.t) loc =
let doc_with_leading_comments = print_leading_comments doc tbl.leading loc in
print_trailing_comments doc_with_leading_comments tbl.trailing loc
(* Prints [doc] with the comments of [loc], followed by [marker], a token that
the source writes right after [loc] (such as [=?]). The trailing comments
adjacent to [loc] precede the marker in the source and print before it; the
remaining trailing comments print after it. *)
let print_comments_before_marker doc (tbl : Comment_table.t) loc marker =
let after_marker =
match Hashtbl.find_opt tbl.trailing loc with
| None -> []
| Some comments ->
let before_marker, after_marker =
Comment_table.partition_adjacent_trailing loc comments
in
Hashtbl.replace tbl.trailing loc before_marker;
after_marker
in
let doc = Doc.concat [print_comments doc tbl loc; marker] in
match after_marker with
| [] -> doc
| _ ->
Hashtbl.replace tbl.trailing loc after_marker;
print_trailing_comments doc tbl.trailing loc
(* [~space_multiline] adds a blank line next to nodes printed on several lines *)
let print_list ~get_loc ~nodes ~print ?(force_break = false)
?(space_multiline = false) t =
let rec loop first_loc_opt prev_loc_opt acc nodes =
match nodes with
| [] -> (first_loc_opt, prev_loc_opt, Doc.concat (List.rev acc))
| node :: nodes ->
let loc = get_loc node in
let start_pos =
match get_first_leading_comment t loc with
| None -> loc.loc_start
| Some comment -> (Comment.loc comment).loc_start
in
let raw_doc = print node t in
if Doc.is_nil raw_doc then loop first_loc_opt prev_loc_opt acc nodes
else
let doc = print_comments raw_doc t loc in
let acc =
match (prev_loc_opt, acc) with
| Some prev_loc, prev_doc :: _ ->
let sep =
if start_pos.pos_lnum - prev_loc.Location.loc_end.pos_lnum > 1
then Doc.concat [Doc.hard_line; Doc.hard_line]
else if space_multiline then
Doc.item_separator ~prev:prev_doc ~next:doc
else Doc.hard_line
in
doc :: sep :: acc
| _ -> [doc]
in
let first_loc_opt =
match first_loc_opt with
| None -> Some loc
| Some _ -> first_loc_opt
in
loop first_loc_opt (Some loc) acc nodes
in
let first_loc_opt, last_loc_opt, docs = loop None None [] nodes in
match (first_loc_opt, last_loc_opt) with
| Some first_loc, Some last_loc ->
let force_break =
force_break || first_loc.loc_start.pos_lnum != last_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break docs
| _ -> Doc.nil
let print_listi ~get_loc ~nodes ~print ?(ignore_empty_lines = false)
?(force_break = false) t =
let rec loop i first_loc_opt prev_loc_opt acc nodes =
match nodes with
| [] -> (first_loc_opt, prev_loc_opt, Doc.concat (List.rev acc))
| node :: nodes ->
let loc = get_loc node in
let start_pos =
match get_first_leading_comment t loc with
| None -> loc.loc_start
| Some comment -> (Comment.loc comment).loc_start
in
let raw_doc = print node t i in
if Doc.is_nil raw_doc then loop i first_loc_opt prev_loc_opt acc nodes
else
let doc = print_comments raw_doc t loc in
let acc =
match prev_loc_opt with
| None -> [doc]
| Some prev_loc ->
let sep =
if
start_pos.pos_lnum - prev_loc.Location.loc_end.pos_lnum > 1
&& not ignore_empty_lines
then Doc.concat [Doc.hard_line; Doc.hard_line]
else Doc.line
in
doc :: sep :: acc
in
let first_loc_opt =
match first_loc_opt with
| None -> Some loc
| Some _ -> first_loc_opt
in
loop (i + 1) first_loc_opt (Some loc) acc nodes
in
let first_loc_opt, last_loc_opt, docs = loop 0 None None [] nodes in
match (first_loc_opt, last_loc_opt) with
| Some first_loc, Some last_loc ->
let force_break =
force_break || first_loc.loc_start.pos_lnum != last_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break docs
| _ -> Doc.nil
let rec print_longident_aux accu = function
| Longident.Lident s -> Doc.text s :: accu
| Ldot (lid, s) -> print_longident_aux (Doc.text s :: accu) lid
let print_longident = function
| Longident.Lident txt -> Doc.text txt
| lid -> Doc.join ~sep:Doc.dot (print_longident_aux [] lid)
type identifier_style = UppercaseExoticIdent | ExoticIdent | NormalIdent
let classify_ident_content ?(allow_uident = false) ?(allow_hyphen = false) txt =
if Token.is_keyword_txt txt then ExoticIdent
else
let len = String.length txt in
let rec loop i =
if i == len then NormalIdent
else if i == 0 then
match String.unsafe_get txt i with
| '\\' -> UppercaseExoticIdent
| 'A' .. 'Z' when allow_uident -> loop (i + 1)
| 'a' .. 'z' | '$' -> loop (i + 1)
(* The scanner reads [_] as an identifier only when one of these
follows; otherwise it is a wildcard. *)
| '_' when len > 1 -> (
match String.unsafe_get txt 1 with
| 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '_' | '$' -> loop (i + 1)
| _ -> ExoticIdent)
| _ -> ExoticIdent
else
match String.unsafe_get txt i with
| 'A' .. 'Z' | 'a' .. 'z' | '0' .. '9' | '\'' | '_' | '$' -> loop (i + 1)
| '-' when allow_hyphen -> loop (i + 1)
| _ -> ExoticIdent
in
loop 0
let print_ident_like ?allow_uident ?allow_hyphen txt =
match classify_ident_content ?allow_uident ?allow_hyphen txt with
| ExoticIdent -> Doc.concat [Doc.text "\\\""; Doc.text txt; Doc.text "\""]
| UppercaseExoticIdent | NormalIdent -> Doc.text txt
let rec unsafe_for_all_range s ~start ~finish p =
start > finish
|| p (String.unsafe_get s start)
&& unsafe_for_all_range s ~start:(start + 1) ~finish p
let for_all_from s start p =
let len = String.length s in
unsafe_for_all_range s ~start ~finish:(len - 1) p
(* See https://github.com/rescript-lang/rescript-compiler/blob/726cfa534314b586e5b5734471bc2023ad99ebd9/jscomp/ext/ext_string.ml#L510 *)
let is_valid_numeric_polyvar_number (x : string) =
let len = String.length x in
len > 0
&&
let a = Char.code (String.unsafe_get x 0) in
a <= 57
&&
if len > 1 then
a > 48
&& for_all_from x 1 (function
| '0' .. '9' -> true
| _ -> false)
else a >= 48
(* Exotic identifiers in poly-vars have a "lighter" syntax: #"ease-in" *)
let print_poly_var_ident txt =
(* numeric poly-vars don't need quotes: #644 *)
if is_valid_numeric_polyvar_number txt then Doc.text txt
else
match classify_ident_content ~allow_uident:true txt with
| UppercaseExoticIdent ->
let len = String.length txt in
(* UppercaseExoticIdent follows the \"..." format,
so removing the leading backslash is enough to transform it into polyvar style *)
Doc.text ((String.sub [@doesNotRaise]) txt 1 (len - 1))
| ExoticIdent -> Doc.concat [Doc.text "\""; Doc.text txt; Doc.text "\""]
| NormalIdent -> (
match txt with
| "" -> Doc.concat [Doc.text "\""; Doc.text txt; Doc.text "\""]
| _ -> Doc.text txt)
let polyvar_ident_to_string poly_var_ident =
Doc.concat [Doc.text "#"; print_poly_var_ident poly_var_ident]
|> Doc.to_string ~width:80
let find_inline_record_definition inline_record_name
(inline_record_definitions : Parsetree.type_declaration list option) =
match inline_record_definitions with
| None -> None
| Some inline_record_definitions ->
inline_record_definitions
|> List.find_opt (fun (r : Parsetree.type_declaration) ->
r.ptype_name.txt = inline_record_name)
let pending_inline_record_definitions inline_record_definitions =
let external_name_of_type_declaration
(type_declaration : Parsetree.type_declaration) =
match String.index_opt type_declaration.ptype_name.txt '.' with
| None -> None
| Some index -> Some (String.sub type_declaration.ptype_name.txt 0 index)
in
match inline_record_definitions with
| [] -> None
| first :: rest -> (
match external_name_of_type_declaration first with
| None -> None
| Some external_name ->
if
List.for_all
(fun type_declaration ->
external_name_of_type_declaration type_declaration
= Some external_name)
rest
then Some (external_name, inline_record_definitions)
else None)
let print_lident l =
let flat_lid lid =
let rec flat accu = function
| Longident.Lident s -> s :: accu
| Ldot (lid, s) -> flat (s :: accu) lid
in
flat [] lid
in
match l with
| Longident.Lident txt -> print_ident_like txt
| Longident.Ldot (path, txt) ->
Doc.concat
[
Doc.join ~sep:Doc.dot (List.map Doc.text (flat_lid path));
Doc.dot;
print_ident_like txt;
]
let print_longident_location l cmt_tbl =
let doc = print_longident l.Location.txt in
print_comments doc cmt_tbl l.loc
(* Module.SubModule.x *)
let print_lident_path path cmt_tbl =
let doc = print_lident path.Location.txt in
print_comments doc cmt_tbl path.loc
(* Module.SubModule.x or Module.SubModule.X *)
let print_ident_path path cmt_tbl =
let doc = print_lident path.Location.txt in
print_comments doc cmt_tbl path.loc
let print_string_loc sloc cmt_tbl =
let doc = print_ident_like sloc.Location.txt in
print_comments doc cmt_tbl sloc.loc
let print_string_contents txt =
let lines = String.split_on_char '\n' txt in
Doc.join ~sep:Doc.literal_line (List.map Doc.text lines)
let interleave_template_parts sources values =
let rec loop acc sources values =
match (sources, values) with
| [source], [] -> Doc.concat [acc; source]
| source :: sources, value :: values ->
loop (Doc.concat [acc; source; value]) sources values
| _ -> assert false
in
loop Doc.nil sources values
let print_constant c =
match c with
| Parsetree.Pconst_integer (s, suffix) -> (
match suffix with
| Some c -> Doc.text (s ^ Char.escaped c)
| None -> Doc.text s)
| Pconst_string payload ->
Doc.concat
[
Doc.text "\"";
print_string_contents (String_literal.string_source payload);
Doc.text "\"";
]
| Pconst_char {source} ->
Doc.concat [Doc.text "'"; Doc.text source; Doc.text "'"]
| Pconst_float (s, _) -> Doc.text s
module State = struct
let custom_layout_threshold = 2
type pending_inline_record_definitions = {
external_name: string;
definitions: Parsetree.type_declaration list;
}
type t = {
custom_layout: int;
mutable pending_inline_record_defs_for_external:
pending_inline_record_definitions option;
}
let init () =
{custom_layout = 0; pending_inline_record_defs_for_external = None}
let next_custom_layout t =
{
custom_layout = t.custom_layout + 1;
pending_inline_record_defs_for_external =
t.pending_inline_record_defs_for_external;
}
let should_break_callback t = t.custom_layout > custom_layout_threshold
end
let rec print_structure ~state (s : Parsetree.structure) t =
match s with
| [] -> print_comments_inside_file t
| structure ->
let previous_can_continue = ref false in
print_list ~space_multiline:true
~get_loc:(fun s -> s.Parsetree.pstr_loc)
~nodes:structure
~print:(fun item cmt_tbl ->
let protect_regexp = !previous_can_continue in
(previous_can_continue :=
match item.Parsetree.pstr_desc with
| Pstr_value _ | Pstr_eval _ -> true
| _ -> false);
print_structure_item ~state ~protect_regexp item cmt_tbl)
t
and print_statement_expr ~protect_regexp expr kind doc =
match kind with
| Parens.Parenthesized -> add_parens doc
| Braced braces -> print_braces doc expr braces
| Nothing when protect_regexp && Parens.starts_with_regexp expr ->
add_statement_parens doc
| Nothing -> doc
and print_structure_item ~state ?(protect_regexp = false)
(si : Parsetree.structure_item) cmt_tbl =
let clear_pending () =
state.State.pending_inline_record_defs_for_external <- None
in
match si.pstr_desc with
| Pstr_value (rec_flag, value_bindings) ->
clear_pending ();
let rec_flag =
match rec_flag with
| Asttypes.Nonrecursive -> Doc.nil
| Asttypes.Recursive -> Doc.text "rec "
in
print_value_bindings ~state ~rec_flag value_bindings cmt_tbl
| Pstr_type (rec_flag, type_declarations) ->
print_type_declarations ~state ~rec_flag type_declarations cmt_tbl
| Pstr_primitive value_description ->
print_value_description ~state value_description cmt_tbl
| Pstr_eval (expr, attrs) ->
clear_pending ();
let expr_doc =
let protect_regexp = protect_regexp && attrs = [] in
let doc = print_expression_with_comments ~state expr cmt_tbl in
print_statement_expr ~protect_regexp expr (Parens.structure_expr expr) doc
in
Doc.concat [print_attributes ~state attrs cmt_tbl; expr_doc]
| Pstr_attribute attr ->
clear_pending ();
fst (print_attribute ~state ~standalone:true attr cmt_tbl)
| Pstr_raw {name_loc; source; attrs} ->
clear_pending ();
Doc.concat
[
print_attributes ~state attrs cmt_tbl;
print_extension ~state ~at_module_lvl:true
(Ast_raw.to_extension ~name:"raw" ~name_loc source)
cmt_tbl;
]
| Pstr_extension (extension, attrs) ->
clear_pending ();
Doc.concat
[
print_attributes ~state attrs cmt_tbl;
Doc.concat
[print_extension ~state ~at_module_lvl:true extension cmt_tbl];
]
| Pstr_include include_declaration ->
clear_pending ();
print_include_declaration ~state include_declaration cmt_tbl
| Pstr_open open_description ->
clear_pending ();
print_open_description ~state open_description cmt_tbl
| Pstr_modtype mod_type_decl ->
clear_pending ();
print_module_type_declaration ~state mod_type_decl cmt_tbl
| Pstr_module module_binding ->
clear_pending ();
print_module_binding ~state ~is_rec:false module_binding cmt_tbl 0
| Pstr_recmodule module_bindings ->
clear_pending ();
print_listi
~get_loc:(fun mb -> mb.Parsetree.pmb_loc)
~nodes:module_bindings
~print:(print_module_binding ~state ~is_rec:true)
cmt_tbl
| Pstr_exception extension_constructor ->
clear_pending ();
print_exception_def ~state extension_constructor cmt_tbl
| Pstr_typext type_extension ->
clear_pending ();
print_type_extension ~state type_extension cmt_tbl
and print_type_extension ~state (te : Parsetree.type_extension) cmt_tbl =
let prefix = Doc.text "type " in
let name = print_lident_path te.ptyext_path cmt_tbl in
let type_params = print_type_params ~state te.ptyext_params cmt_tbl in
let extension_constructors =
let ecs = te.ptyext_constructors in
let force_break =
match (ecs, List.rev ecs) with
| first :: _, last :: _ ->
first.pext_loc.loc_start.pos_lnum > te.ptyext_path.loc.loc_end.pos_lnum
|| first.pext_loc.loc_start.pos_lnum < last.pext_loc.loc_end.pos_lnum
| _ -> false
in
let private_flag =
match te.ptyext_private with
| Asttypes.Private -> Doc.concat [Doc.text "private"; Doc.line]
| Public -> Doc.nil
in
let rows =
print_listi
~get_loc:(fun n -> n.Parsetree.pext_loc)
~print:(print_extension_constructor ~state)
~nodes:ecs ~force_break cmt_tbl
in
Doc.breakable_group ~force_break
(Doc.indent (Doc.concat [Doc.line; private_flag; rows]))
in
Doc.group
(Doc.concat
[
print_attributes ~state ~loc:te.ptyext_path.loc te.ptyext_attributes
cmt_tbl;
prefix;
name;
type_params;
Doc.text " +=";
extension_constructors;
])
and print_module_binding ~state ~is_rec module_binding cmt_tbl i =
let prefix =
if i = 0 then
Doc.concat
[Doc.text "module "; (if is_rec then Doc.text "rec " else Doc.nil)]
else Doc.text "and "
in
let mod_expr_doc, mod_constraint_doc =
match module_binding.pmb_expr with
| {pmod_desc = Pmod_constraint (mod_expr, mod_type)} as constrained
(* [module M: S = E] has no node for the constraint's attributes *)
when not (Parsetree_viewer.mod_expr_has_attributes constrained) ->
let mod_expr_doc =
if Parens.mod_expr_parens constrained then
add_mod_expr_parens (print_mod_expr ~state mod_expr cmt_tbl)
else print_mod_expr_constraint_parens ~state mod_expr cmt_tbl
in
( mod_expr_doc,
Doc.concat [Doc.text ": "; print_mod_type ~state mod_type cmt_tbl] )
| mod_expr -> (print_mod_expr ~state mod_expr cmt_tbl, Doc.nil)
in
let mod_name =
let doc = Doc.text module_binding.pmb_name.Location.txt in
print_comments doc cmt_tbl module_binding.pmb_name.loc
in
let doc =
Doc.concat
[
print_attributes ~state ~loc:module_binding.pmb_name.loc
module_binding.pmb_attributes cmt_tbl;
prefix;
mod_name;
mod_constraint_doc;
Doc.text " = ";
mod_expr_doc;
]
in
print_comments doc cmt_tbl module_binding.pmb_loc
and print_module_type_declaration ~state
(mod_type_decl : Parsetree.module_type_declaration) cmt_tbl =
let mod_name =
let doc = Doc.text mod_type_decl.pmtd_name.txt in
print_comments doc cmt_tbl mod_type_decl.pmtd_name.loc
in
Doc.concat
[
print_attributes ~state mod_type_decl.pmtd_attributes cmt_tbl;
Doc.text "module type ";
mod_name;
(match mod_type_decl.pmtd_type with
| None -> Doc.nil
| Some mod_type ->
Doc.concat [Doc.text " = "; print_mod_type ~state mod_type cmt_tbl]);
]
and print_mod_type ~state mod_type cmt_tbl =
let mod_type_doc =
match mod_type.pmty_desc with
| Parsetree.Pmty_ident longident ->
Doc.concat
[
print_attributes ~state ~loc:longident.loc mod_type.pmty_attributes
cmt_tbl;
print_longident_location longident cmt_tbl;
]
| Pmty_signature [] ->
if has_comments_inside cmt_tbl mod_type.pmty_loc then
let doc = print_comments_inside cmt_tbl mod_type.pmty_loc in
Doc.concat [Doc.lbrace; doc; Doc.rbrace]
else
let should_break =
mod_type.pmty_loc.loc_start.pos_lnum
< mod_type.pmty_loc.loc_end.pos_lnum
in
Doc.breakable_group ~force_break:should_break
(Doc.concat [Doc.lbrace; Doc.soft_line; Doc.soft_line; Doc.rbrace])
| Pmty_signature signature ->
let signature_doc =
Doc.breakable_group ~force_break:true
(Doc.concat
[
Doc.lbrace;
Doc.indent
(Doc.concat
[Doc.line; print_signature ~state signature cmt_tbl]);
Doc.line;
Doc.rbrace;
])
in
Doc.concat
[
print_attributes ~state mod_type.pmty_attributes cmt_tbl; signature_doc;
]
| Pmty_functor _ ->
let parameters, return_type = Parsetree_viewer.functor_type mod_type in
let parameters_doc =
match parameters with
| [] -> Doc.nil
| [(attrs, {Location.txt = "_"; loc}, Some mod_type)] ->
let cmt_loc =
{loc with loc_end = mod_type.Parsetree.pmty_loc.loc_end}
in
let attrs = print_attributes ~state attrs cmt_tbl in
let doc =
Doc.concat [attrs; print_mod_type ~state mod_type cmt_tbl]
in
print_comments doc cmt_tbl cmt_loc
| [(attrs, {Location.txt = "*"; loc}, None)] ->
let attrs = print_attributes ~state attrs cmt_tbl in
let doc = Doc.concat [attrs; Doc.text "()"] in
print_comments doc cmt_tbl loc
| params ->
Doc.group
(Doc.concat
[
Doc.lparen;
Doc.indent
(Doc.concat
[
Doc.soft_line;
Doc.join
~sep:(Doc.concat [Doc.comma; Doc.line])
(List.map
(fun (attrs, lbl, mod_type) ->
let cmt_loc =
match mod_type with
| None -> lbl.Asttypes.loc
| Some mod_type ->
{
lbl.Asttypes.loc with
loc_end =
mod_type.Parsetree.pmty_loc.loc_end;
}
in
let attrs =
print_attributes ~state attrs cmt_tbl
in
let lbl_doc =
if lbl.Location.txt = "_" then Doc.nil
else if lbl.txt = "*" then
let doc = Doc.text "()" in
print_comments doc cmt_tbl lbl.loc
else
let doc = Doc.text lbl.txt in
print_comments doc cmt_tbl lbl.loc
in
let doc =
Doc.concat
[
attrs;
lbl_doc;
(match mod_type with
| None -> Doc.nil
| Some mod_type ->
Doc.concat
[
(if lbl.txt = "_" then Doc.nil
else Doc.text ": ");
print_mod_type ~state mod_type
cmt_tbl;
]);
]
in